From a30f2bcae8ffe8c9c9b1f3aa439c30ad1aac7417 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 16 Apr 2024 12:42:15 +0200 Subject: [PATCH 01/65] start panda3D cmake and integration --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b94225d85d..a0d4086ae5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,6 +623,7 @@ VP_OPTION(USE_DIRECTSHOW DIRECTSHOW "" "Include dshow support" " VP_OPTION(USE_OPENMP OpenMP "" "Include openmp support" "" ON) VP_OPTION(USE_EIGEN3 Eigen3 QUIET "Include eigen3 support" "" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_COIN3D "Coin3D;MyCoin3D" "" "Include coin3d support" "" ON IF OPENGL_FOUND AND NOT WINRT AND NOT IOS) +VP_OPTION(USE_PANDA3D "Panda3D;MyPanda3D" "" "Include Panda3D support" "" ON IF NOT IOS) VP_OPTION(USE_YARP YARP QUIET "Include yarp support" "YARP_DIR" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_OGRE OGRE QUIET "Include Ogre support" "OGRE_DIR" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_OIS OIS QUIET "Include Ogre/ois support" "OIS_DIR" ON IF USE_OGRE AND NOT WINRT AND NOT IOS) From 508415372d46088e84e5ae151e3f14ad4a657860 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 16 Apr 2024 16:08:42 +0200 Subject: [PATCH 02/65] Initial version of the Panda3D find cmake --- CMakeLists.txt | 6 ++++- cmake/FindMyPanda3D.cmake | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 cmake/FindMyPanda3D.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a0d4086ae5..f6d0a5b8c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,7 +623,7 @@ VP_OPTION(USE_DIRECTSHOW DIRECTSHOW "" "Include dshow support" " VP_OPTION(USE_OPENMP OpenMP "" "Include openmp support" "" ON) VP_OPTION(USE_EIGEN3 Eigen3 QUIET "Include eigen3 support" "" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_COIN3D "Coin3D;MyCoin3D" "" "Include coin3d support" "" ON IF OPENGL_FOUND AND NOT WINRT AND NOT IOS) -VP_OPTION(USE_PANDA3D "Panda3D;MyPanda3D" "" "Include Panda3D support" "" ON IF NOT IOS) +VP_OPTION(USE_PANDA3D "MyPanda3D" "" "Include Panda3D support" "" ON IF NOT IOS) VP_OPTION(USE_YARP YARP QUIET "Include yarp support" "YARP_DIR" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_OGRE OGRE QUIET "Include Ogre support" "OGRE_DIR" ON IF NOT WINRT AND NOT IOS) VP_OPTION(USE_OIS OIS QUIET "Include Ogre/ois support" "OIS_DIR" ON IF USE_OGRE AND NOT WINRT AND NOT IOS) @@ -1083,6 +1083,7 @@ VP_SET(VISP_HAVE_SOWIN TRUE IF (BUILD_MODULE_visp_ar AND USE_SOWIN)) VP_SET(VISP_HAVE_SOXT TRUE IF (BUILD_MODULE_visp_ar AND USE_SOXT)) VP_SET(VISP_HAVE_SOQT TRUE IF (BUILD_MODULE_visp_ar AND USE_SOQT)) VP_SET(VISP_HAVE_QT TRUE IF (BUILD_MODULE_visp_ar AND USE_QT)) +VP_SET(VISP_HAVE_PANDA3D TRUE IF (BUILD_MODULE_visp_ar AND USE_PANDA3D)) VP_SET(VISP_HAVE_ZBAR TRUE IF (BUILD_MODULE_visp_detection AND USE_ZBAR)) VP_SET(VISP_HAVE_DMTX TRUE IF (BUILD_MODULE_visp_detection AND USE_DMTX)) @@ -1728,6 +1729,9 @@ endif() status(" \\- Use Qt5:" USE_SOQT AND SoQt_FOUND THEN "yes" ELSE "no") status(" \\- Use Qt4:" USE_QT AND DESIRED_QT_VERSION MATCHES 4 THEN "yes" ELSE "no") status(" \\- Use Qt3:" USE_QT AND DESIRED_QT_VERSION MATCHES 3 THEN "yes" ELSE "no") + +status(" Panda3D:" USE_PANDA3D AND Panda3D_FOUND THEN "yes" ELSE "no") + status("") status(" Media I/O: ") status(" Use JPEG:" USE_JPEG THEN "yes (ver ${JPEG_LIB_VERSION})" ELSE "no") diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake new file mode 100644 index 0000000000..81d1be37ac --- /dev/null +++ b/cmake/FindMyPanda3D.cmake @@ -0,0 +1,50 @@ +# First option: panda3d built through makepanda +set(Panda3D_DIR) +if(${Panda3D_DIR}) +else() + set(Panda3D_DIR "$ENV{Panda3D_DIR}") +endif() + +set(PANDA3D_LIBS + panda p3framework p3direct + p3dtoolconfig p3dtool pandaexpress pandaegg + #p3ffmpeg p3interrogatedb p3tinydisplay p3vision + #pandaai pandafx pandaphysics pandaskel +) + +# Panda3D_DIR should point to the "built" directory generated by makepanda +if(Panda3D_DIR) + # Find all libraries + set(LIBRARY_PATHS "${Panda3D_DIR}/lib") + set(INCLUDE_PATHS "${Panda3D_DIR}/include") + set(Panda3D_BINARY_DIR "${Panda3D_DIR}/bin") + +else() # Check for installed Panda3D + set(LIBRARY_PATHS "/usr/lib" "/usr/lib/panda3d" "/usr/lib/x86_64-linux-gnu/panda3d") + set(INCLUDE_PATHS "/usr/include/panda3d") + set(Panda3D_BINARY_DIR "/usr/bin") +endif() + + +set(Panda3D_LIBRARIES "") +set(ALL_LIBS_FOUND TRUE) +foreach(lib_name ${PANDA3D_LIBS}) + find_library(Panda3D_${lib_name}_LIBRARY ${lib_name} PATHS ${LIBRARY_PATHS}) + if(Panda3D_${lib_name}_LIBRARY EQUAL "Panda3D_${lib_name}_LIBRARY-NOTFOUND") + set(ALL_LIBS_FOUND FALSE) + else() + list(APPEND Panda3D_LIBRARIES "${Panda3D_${lib_name}_LIBRARY}") + endif() + mark_as_advanced(Panda3D_${lib_name}_LIBRARY) +endforeach() + +find_path(Panda3D_INCLUDE_DIRS panda.h PATHS ${INCLUDE_PATHS}) + +include(FindPackageHandleStandardArgs) +# Handle the QUIETLY and REQUIRED arguments and set the DUMMYSDK_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(Panda3D DEFAULT_MSG ALL_LIBS_FOUND Panda3D_INCLUDE_DIRS) +if(Panda3D_FOUND) + mark_as_advanced(Panda3D_LIBRARIES Panda3D_INCLUDE_DIRS Panda3D_BINARY_DIR) + +endif() From 8a63d3ecbc676d7e5112c3c0a0cfbf86886a3744 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 16 Apr 2024 16:16:36 +0200 Subject: [PATCH 03/65] Add VISP_HAVE_PANDA3D_FLAG --- cmake/FindMyPanda3D.cmake | 7 +++---- cmake/templates/VISPConfig.cmake.in | 1 + cmake/templates/vpConfig.h.in | 3 +++ doc/config-doxygen.in | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index 81d1be37ac..b81e598e53 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -1,4 +1,3 @@ -# First option: panda3d built through makepanda set(Panda3D_DIR) if(${Panda3D_DIR}) else() @@ -12,20 +11,20 @@ set(PANDA3D_LIBS #pandaai pandafx pandaphysics pandaskel ) +# First option: built with makepanda # Panda3D_DIR should point to the "built" directory generated by makepanda if(Panda3D_DIR) - # Find all libraries set(LIBRARY_PATHS "${Panda3D_DIR}/lib") set(INCLUDE_PATHS "${Panda3D_DIR}/include") set(Panda3D_BINARY_DIR "${Panda3D_DIR}/bin") -else() # Check for installed Panda3D +else() # Otherwise, check for installed Panda3D set(LIBRARY_PATHS "/usr/lib" "/usr/lib/panda3d" "/usr/lib/x86_64-linux-gnu/panda3d") set(INCLUDE_PATHS "/usr/include/panda3d") set(Panda3D_BINARY_DIR "/usr/bin") endif() - +# Fetch all libraries set(Panda3D_LIBRARIES "") set(ALL_LIBS_FOUND TRUE) foreach(lib_name ${PANDA3D_LIBS}) diff --git a/cmake/templates/VISPConfig.cmake.in b/cmake/templates/VISPConfig.cmake.in index b51ca8afa2..1fb123bc9f 100644 --- a/cmake/templates/VISPConfig.cmake.in +++ b/cmake/templates/VISPConfig.cmake.in @@ -240,6 +240,7 @@ set(VISP_HAVE_OPENCV_NONFREE "@VISP_HAVE_OPENCV_NONFREE@") set(VISP_HAVE_OPENCV_VERSION "@VISP_HAVE_OPENCV_VERSION@") set(VISP_HAVE_OPENGL "@VISP_HAVE_OPENGL@") set(VISP_HAVE_OPENMP "@VISP_HAVE_OPENMP@") +set(VISP_HAVE_PANDA3D "@VISP_HAVE_PANDA3D@") set(VISP_HAVE_PARPORT "@VISP_HAVE_PARPORT@") set(VISP_HAVE_PCL "@VISP_HAVE_PCL@") set(VISP_HAVE_PIONEER "@VISP_HAVE_PIONEER@") diff --git a/cmake/templates/vpConfig.h.in b/cmake/templates/vpConfig.h.in index 7ff33610b8..1265ab9e48 100644 --- a/cmake/templates/vpConfig.h.in +++ b/cmake/templates/vpConfig.h.in @@ -283,6 +283,9 @@ // Defined if OpenGL library available. #cmakedefine VISP_HAVE_OPENGL +// Defined if Panda3D is available +#cmakedefine VISP_HAVE_PANDA3D + // Defined if Qt library available (either Qt-3 or Qt-4). #cmakedefine VISP_HAVE_QT diff --git a/doc/config-doxygen.in b/doc/config-doxygen.in index 0f98afd95e..aa39081963 100644 --- a/doc/config-doxygen.in +++ b/doc/config-doxygen.in @@ -2409,6 +2409,7 @@ PREDEFINED = @DOXYGEN_SHOULD_SKIP_THIS@ \ HAVE_OPENCV_VIDEO \ HAVE_OPENCV_VIDEOIO \ VISP_HAVE_OPENGL \ + VISP_HAVE_PANDA3D \ VISP_HAVE_PARPORT \ VISP_HAVE_PCL \ VISP_HAVE_PIONEER \ From ddb7f8e8afe6f1b59e9dfc3b66f2cf4c87b736aa Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 16 Apr 2024 19:20:10 +0200 Subject: [PATCH 04/65] Creating base panda3d classes, along with simple tutorial --- cmake/FindMyPanda3D.cmake | 5 +- modules/ar/CMakeLists.txt | 9 + .../include/visp3/ar/vpPanda3DBaseRenderer.h | 228 ++++++++++++++++++ tutorial/CMakeLists.txt | 1 + tutorial/ar/CMakeLists.txt | 24 ++ tutorial/ar/data/simple_cube.mtl | 12 + tutorial/ar/data/simple_cube.obj | 46 ++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 32 +++ 8 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h create mode 100644 tutorial/ar/CMakeLists.txt create mode 100644 tutorial/ar/data/simple_cube.mtl create mode 100644 tutorial/ar/data/simple_cube.obj create mode 100644 tutorial/ar/tutorial-panda3d-renderer.cpp diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index b81e598e53..0433802175 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -5,8 +5,9 @@ else() endif() set(PANDA3D_LIBS - panda p3framework p3direct - p3dtoolconfig p3dtool pandaexpress pandaegg + panda p3framework pandaexpress + p3dtoolconfig p3dtool p3direct + #pandaegg #p3ffmpeg p3interrogatedb p3tinydisplay p3vision #pandaai pandafx pandaphysics pandaskel ) diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 004b702c55..cad171d91d 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -173,6 +173,15 @@ if(USE_COIN3D) endif() endif() +if(USE_PANDA3D) + if(Panda3D_INCLUDE_DIRS) + list(APPEND opt_incs ${Panda3D_INCLUDE_DIRS}) + endif() + if(Panda3D_LIBRARIES) + list(APPEND opt_libs ${Panda3D_LIBRARIES}) + endif() +endif() + vp_add_module(ar visp_core) vp_glob_module_sources() diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h new file mode 100644 index 0000000000..84262ed55e --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -0,0 +1,228 @@ +#ifndef vpPanda3DBaseRenderer_h +#define vpPanda3DBaseRenderer_h + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include + +#include +#include +#include + + + +/** + * @brief Rendering parameters for a panda3D simulation + * + * includes: + * - Camera intrinsics + * - Image resolution + * - Clipping parameters + */ +class VISP_EXPORT vpPanda3DRenderParameters +{ +public: + vpPanda3DRenderParameters() : m_cam(), m_height(0), m_width(0), m_clipNear(0.001), m_clipFar(10.0) { } + vpPanda3DRenderParameters(const vpCameraParameters &cam, unsigned int h, unsigned int w, + double clipNear, double clipFar) + : m_cam(cam), m_height(h), m_width(w), m_clipNear(clipNear), m_clipFar(clipFar) + { } + + /** + * @brief Retrieve camera intrinsics. + * + * @return const vpCameraParameters& + */ + const vpCameraParameters &getCameraIntrinsics() const { return m_cam; } + /** + * @brief set camera intrinsics. Only camera intrinsics for a lens without distortion are supported. + * \throws if camera intrinsics have a distortion model. + */ + void setCameraIntrinsics(const vpCameraParameters &cam) + { + if (cam.get_projModel() != vpCameraParameters::perspectiveProjWithoutDistortion) { + throw vpException(vpException::badValue, "Panda3D renderer: only lenses with no distortion are supported"); + } + m_cam = cam; + } + + double getNearClippingDistance() const { return m_clipNear; } + double getFarClippingDistance() const { return m_clipFar; } + + void setClippingDistance(double near, double far) + { + if (far < near) { + std::swap(near, far); + } + m_clipNear = near; + m_clipFar = far; + } + + unsigned int getImageWidth() const { return m_width; } + unsigned int getImageHeight() const { return m_height; } + + void setImageResolution(unsigned int height, unsigned int width) + { + m_height = height; + m_width = width; + } + + void setupPandaCamera(Camera *camera) + { + // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), + // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 + // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl + // near, far = self.z_near, self.z_far + // lens.set_far(far) + // lens.set_near(near) + + // h, w = self.resolution + // fx = self.K[0, 0] + // fy = self.K[1, 1] + // cx = self.K[0, 2] + // cy = h - self.K[1, 2] + // A = (far + near) / (far - near) + // B = -2 * (far * near) / (far - near) + // user_mat = np.array( + // [ + // [fx, 0, 0, 0], + // [0, 0, A, 1], + // [0, fy, 0, 0], + // [0, 0, B, 0], + // ] + // ) + + // lens.setFilmSize(w, h) + // lens.setUserMat(p3d.core.LMatrix4f(*user_mat.flatten().tolist())) + // lens.setFilmOffset(w * 0.5 - cx, h * 0.5 - cy) + // return + PT(MatrixLens) lens = new MatrixLens(); + lens->set_near_far(m_clipNear, m_clipFar); + const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); + const double B = -2 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); + + const double cx = m_cam.get_u0(); + const double cy = m_height - m_cam.get_v0(); + lens->set_film_size(m_width, m_height); + lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); + lens->set_user_mat(LMatrix4( + m_cam.get_px(), 0, 0, 0, + 0, 0, A, 1, + 0, m_cam.get_py(), 0, 0, + 0, 0, B, 0 + )); + + camera->set_lens(lens); + } + +private: + vpCameraParameters m_cam; + unsigned int m_height, m_width; + double m_clipNear, m_clipFar; +}; + + +/** + * @brief Base class for a panda3D renderer. This class handles basic functionalities, + * such as loading object, changing camera parameters etc. + * + */ +class VISP_EXPORT vpPanda3DBaseRenderer +{ +public: + vpPanda3DBaseRenderer(const std::string &rendererName) : m_name(rendererName), m_framework(nullptr), m_window(nullptr) { } + + virtual void initFramework(bool showWindow) + { + if (m_framework.use_count() > 0) { + throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); + } + m_framework = std::shared_ptr(new PandaFramework()); + m_framework->open_framework(); + WindowProperties winProps; + winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); + int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + m_window = m_framework->open_window(winProps, flags); + m_window->set_background_type(WindowFramework::BackgroundType::BT_black); + m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_renderRoot.set_shader_auto(); + initCamera(); + } + + virtual void renderFrame() + { + m_framework->get_graphics_engine()->render_frame(); + } + + virtual void setCameraPose(const vpHomogeneousMatrix &wTc) + { + if (m_camera.is_null()) { + throw vpException(vpException::notInitialized, "Camera was not initialized before trying to set its pose"); + } + setNodePose(m_cameraPath, wTc); + } + /** + * @brief Set the pose of a node. + * + * @param name Node path to search for, from the render root. See https://docs.panda3d.org/1.10/python/programming/scene-graph/searching-scene-graph + * @param wTo Pose of the object in the world frame + */ + virtual void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) + { + NodePath object = m_renderRoot.find(name); + setNodePose(object, wTo); + } + + virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) + { + vpTranslationVector t = wTo.getTranslationVector(); + vpQuaternionVector q(wTo.getRotationMatrix()); + object.set_pos(t[0], t[1], t[2]); + object.set_quat(LQuaternion(q.w(), q.x(), q.y(), q.z())); + } + + /** + * @brief Initialize camera. Should be called when the scene root of this render has already been created. + * + */ + virtual void initCamera() + { + m_camera = new Camera("camera"); + m_renderRoot.attach_new_node(m_camera); + m_renderParameters.setupPandaCamera(m_camera); + } + + NodePath loadObject(const std::string &nodeName, const std::string &modelPath) + { + NodePath model = m_window->load_model(m_framework->get_models(), modelPath); + std::cout << "After loading model" << std::endl; + model.detach_node(); + model.set_name(nodeName); + return model; + } + + virtual void addNodeToScene(NodePath &object) + { + NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); + objectInScene.set_name(object.get_name()); + } + + + virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms) { m_renderParameters = params; } + + +protected: + const std::string m_name; //! name of the renderer + std::shared_ptr m_framework; //! Pointer to the active panda framework + PT(WindowFramework) m_window; //! Pointer to owning window, which can create buffers etc. It is not necessarily visible. + vpPanda3DRenderParameters m_renderParameters; //! Rendering parameters + NodePath m_renderRoot; //! Node containing all the objects and the camera for this renderer + PT(Camera) m_camera; + NodePath m_cameraPath; //! NodePath of the camera + +}; + + +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index 2441f927e1..a357dbf34d 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -25,6 +25,7 @@ if(MSVC) endif() endif() +visp_add_subdirectory(ar REQUIRED_DEPS visp_core visp_gui visp_ar) visp_add_subdirectory(bridge/opencv REQUIRED_DEPS visp_core visp_io) visp_add_subdirectory(computer-vision REQUIRED_DEPS visp_core visp_blob visp_vision visp_io visp_gui visp_detection visp_sensor) visp_add_subdirectory(grabber REQUIRED_DEPS visp_core visp_sensor visp_io visp_gui) diff --git a/tutorial/ar/CMakeLists.txt b/tutorial/ar/CMakeLists.txt new file mode 100644 index 0000000000..4bdce7fb55 --- /dev/null +++ b/tutorial/ar/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.5) + +project(color_segmentation) + +find_package(VISP REQUIRED visp_core visp_gui visp_ar) + +# set the list of source files +set(tutorial_cpp + tutorial-panda3d-renderer.cpp +) + +# file(GLOB tutorial_data "*.jpg") + +foreach(cpp ${tutorial_cpp}) + visp_add_target(${cpp}) + if(COMMAND visp_add_dependency) + visp_add_dependency(${cpp} "tutorials") + endif() +endforeach() + +# Copy the data files to the same location than the target +# foreach(data ${tutorial_data}) +# visp_copy_data(tutorial-hsv-segmentation-basic.cpp ${data}) +# endforeach() diff --git a/tutorial/ar/data/simple_cube.mtl b/tutorial/ar/data/simple_cube.mtl new file mode 100644 index 0000000000..e7fc01162a --- /dev/null +++ b/tutorial/ar/data/simple_cube.mtl @@ -0,0 +1,12 @@ +# Blender 3.3.1 MTL File: 'None' +# www.blender.org + +newmtl Material +Ns 111.687584 +Ka 0.948187 0.948187 0.948187 +Kd 0.800000 0.013142 0.026189 +Ks 0.691710 0.691710 0.691710 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 3 diff --git a/tutorial/ar/data/simple_cube.obj b/tutorial/ar/data/simple_cube.obj new file mode 100644 index 0000000000..cf6498cfdf --- /dev/null +++ b/tutorial/ar/data/simple_cube.obj @@ -0,0 +1,46 @@ +# Blender 3.3.1 +# www.blender.org +mtllib simple_cube.mtl +o Cube +v 1.000000 1.000000 1.000000 +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -1.0000 -0.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 1.0000 -0.0000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.875000 0.500000 +vt 0.625000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.250000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 0.000000 +vt 0.375000 0.000000 +vt 0.375000 1.000000 +vt 0.125000 0.750000 +s 0 +usemtl Material +f 5/5/1 3/3/1 1/1/1 +f 3/3/2 8/13/2 4/4/2 +f 7/11/3 6/8/3 8/12/3 +f 2/2/4 8/14/4 6/7/4 +f 1/1/5 4/4/5 2/2/5 +f 5/6/6 2/2/6 6/8/6 +f 5/5/1 7/9/1 3/3/1 +f 3/3/2 7/10/2 8/13/2 +f 7/11/3 5/6/3 6/8/3 +f 2/2/4 4/4/4 8/14/4 +f 1/1/5 3/3/5 4/4/5 +f 5/6/6 1/1/6 2/2/6 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp new file mode 100644 index 0000000000..79f05088ea --- /dev/null +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -0,0 +1,32 @@ +#include +#include +#if defined(VISP_HAVE_PANDA3D) + +#include +#include + +int main() +{ + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.001, 1.0); + vpPanda3DBaseRenderer renderer("basic"); + renderer.setRenderParameters(renderParams); + renderer.initFramework(true); + NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); + renderer.addNodeToScene(object); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.75, 0.0, 0.0, 0.0)); + while (true) { + renderer.renderFrame(); + } + return 0; +} + +#else + +int main() +{ + + std::cerr << "Recompile ViSP with Panda3D as a third party to run this tutorial" << std::endl; + return EXIT_FAILURE; +} + +#endif From 195105ff3773da56b887a0fe60e41290e34174bc Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 16 Apr 2024 23:20:15 +0200 Subject: [PATCH 05/65] Basic rendering --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 74 ++++++++++--------- tutorial/ar/CMakeLists.txt | 5 ++ tutorial/ar/data/simple_cube.mtl | 2 +- tutorial/ar/tutorial-panda3d-renderer.cpp | 21 +++++- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 84262ed55e..5aafcb232a 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -73,34 +73,11 @@ class VISP_EXPORT vpPanda3DRenderParameters // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl - // near, far = self.z_near, self.z_far - // lens.set_far(far) - // lens.set_near(near) - - // h, w = self.resolution - // fx = self.K[0, 0] - // fy = self.K[1, 1] - // cx = self.K[0, 2] - // cy = h - self.K[1, 2] - // A = (far + near) / (far - near) - // B = -2 * (far * near) / (far - near) - // user_mat = np.array( - // [ - // [fx, 0, 0, 0], - // [0, 0, A, 1], - // [0, fy, 0, 0], - // [0, 0, B, 0], - // ] - // ) - - // lens.setFilmSize(w, h) - // lens.setUserMat(p3d.core.LMatrix4f(*user_mat.flatten().tolist())) - // lens.setFilmOffset(w * 0.5 - cx, h * 0.5 - cy) - // return + PT(MatrixLens) lens = new MatrixLens(); lens->set_near_far(m_clipNear, m_clipFar); const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); - const double B = -2 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); + const double B = -2.0 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); const double cx = m_cam.get_u0(); const double cy = m_height - m_cam.get_v0(); @@ -145,9 +122,22 @@ class VISP_EXPORT vpPanda3DBaseRenderer int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; m_window = m_framework->open_window(winProps, flags); m_window->set_background_type(WindowFramework::BackgroundType::BT_black); + setupScene(); + setupCamera(); + m_window->get_display_region_3d()->set_camera(m_cameraPath); + } + + virtual void setupScene() + { m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_renderRoot.set_shader_auto(); - initCamera(); + } + + void initFromParent(std::shared_ptr framework, PT(WindowFramework) window) + { + m_framework = framework; + m_window = window; + setupScene(); + setupCamera(); } virtual void renderFrame() @@ -155,6 +145,8 @@ class VISP_EXPORT vpPanda3DBaseRenderer m_framework->get_graphics_engine()->render_frame(); } + NodePath getRenderRoot() { return m_renderRoot; } + virtual void setCameraPose(const vpHomogeneousMatrix &wTc) { if (m_camera.is_null()) { @@ -181,28 +173,44 @@ class VISP_EXPORT vpPanda3DBaseRenderer object.set_pos(t[0], t[1], t[2]); object.set_quat(LQuaternion(q.w(), q.x(), q.y(), q.z())); } + virtual vpHomogeneousMatrix getNodePose(const std::string &name) + { + NodePath object = m_renderRoot.find(name); + return getNodePose(object); + } + virtual vpHomogeneousMatrix getNodePose(NodePath &object) + { + const LPoint3 pos = object.get_pos(); + const LQuaternion quat = object.get_quat(); + const vpTranslationVector t(pos[0], pos[1], pos[2]); + const vpQuaternionVector q(quat.get_i(), quat.get_j(), quat.get_k(), quat.get_r()); + return vpHomogeneousMatrix(t, q); + } + /** * @brief Initialize camera. Should be called when the scene root of this render has already been created. * */ - virtual void initCamera() + virtual void setupCamera() { - m_camera = new Camera("camera"); - m_renderRoot.attach_new_node(m_camera); + m_cameraPath = m_window->make_camera(); + m_camera = (Camera *)m_cameraPath.node(); + // m_camera = m_window->get_camera(0); + m_cameraPath = m_renderRoot.attach_new_node(m_camera); m_renderParameters.setupPandaCamera(m_camera); } NodePath loadObject(const std::string &nodeName, const std::string &modelPath) { - NodePath model = m_window->load_model(m_framework->get_models(), modelPath); + NodePath model = m_window->load_model(m_window->get_render(), modelPath); std::cout << "After loading model" << std::endl; - model.detach_node(); + //model.detach_node(); model.set_name(nodeName); return model; } - virtual void addNodeToScene(NodePath &object) + virtual void addNodeToScene(const NodePath &object) { NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); objectInScene.set_name(object.get_name()); diff --git a/tutorial/ar/CMakeLists.txt b/tutorial/ar/CMakeLists.txt index 4bdce7fb55..1b7c1c7a0a 100644 --- a/tutorial/ar/CMakeLists.txt +++ b/tutorial/ar/CMakeLists.txt @@ -11,6 +11,9 @@ set(tutorial_cpp # file(GLOB tutorial_data "*.jpg") +visp_set_source_file_compile_flag(tutorial-panda3d-renderer.cpp -Wno-extra -Wno-unused-parameter -Wno-unused-variable) + + foreach(cpp ${tutorial_cpp}) visp_add_target(${cpp}) if(COMMAND visp_add_dependency) @@ -18,6 +21,8 @@ foreach(cpp ${tutorial_cpp}) endif() endforeach() + + # Copy the data files to the same location than the target # foreach(data ${tutorial_data}) # visp_copy_data(tutorial-hsv-segmentation-basic.cpp ${data}) diff --git a/tutorial/ar/data/simple_cube.mtl b/tutorial/ar/data/simple_cube.mtl index e7fc01162a..5ac7f9e997 100644 --- a/tutorial/ar/data/simple_cube.mtl +++ b/tutorial/ar/data/simple_cube.mtl @@ -9,4 +9,4 @@ Ks 0.691710 0.691710 0.691710 Ke 0.000000 0.000000 0.000000 Ni 1.450000 d 1.000000 -illum 3 +illum 2 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 79f05088ea..91d86b01b3 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -3,19 +3,36 @@ #if defined(VISP_HAVE_PANDA3D) #include +#include #include +#include int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.001, 1.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.001, 10.0); vpPanda3DBaseRenderer renderer("basic"); renderer.setRenderParameters(renderParams); renderer.initFramework(true); + std::cout << "Loading object" << std::endl; NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); + std::cout << "Adding node to scene" <set_color(LColor(0.2, 0.2, 0.2, 1.0)); + // NodePath alnp = renderer.getRenderRoot().attach_new_node(alight); + // renderer.getRenderRoot().set_light(alnp); + std::cout << "Setting camera pose" << std::endl; + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); while (true) { + const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); + const double afterRender = vpTime::measureTimeMs(); + const double delta = (afterRender - beforeRender) / 1000.0; + vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); + vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + renderer.setNodePose("cube", wTo * oToo); + std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; } return 0; } From cb30042dbe1906b8c39238ee47222011cf341588 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 17 Apr 2024 11:49:35 +0200 Subject: [PATCH 06/65] working on RendererSet --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 9 ++ .../include/visp3/ar/vpPanda3DRendererSet.h | 141 ++++++++++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 8 +- 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DRendererSet.h diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 5aafcb232a..1df6254df1 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -145,6 +145,8 @@ class VISP_EXPORT vpPanda3DBaseRenderer m_framework->get_graphics_engine()->render_frame(); } + + const std::string &getName() const { return m_name; } NodePath getRenderRoot() { return m_renderRoot; } virtual void setCameraPose(const vpHomogeneousMatrix &wTc) @@ -154,6 +156,13 @@ class VISP_EXPORT vpPanda3DBaseRenderer } setNodePose(m_cameraPath, wTc); } + virtual vpHomogeneousMatrix getCameraPose() + { + if (m_camera.is_null()) { + throw vpException(vpException::notInitialized, "Camera was not initialized before trying to get its pose"); + } + return getNodePose(m_cameraPath); + } /** * @brief Set the pose of a node. * diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h new file mode 100644 index 0000000000..d95c6ce4ff --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -0,0 +1,141 @@ +#ifndef vpPanda3DRendererSet_h +#define vpPanda3DRendererSet_h +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include + + +#include + +class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer +{ +public: + vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") + { + m_renderParameters = renderParameters; + } + + void initFramework(bool showWindow) vp_override + { + if (m_framework.use_count() > 0) { + throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); + } + m_framework = std::shared_ptr(new PandaFramework()); + m_framework->open_framework(); + WindowProperties winProps; + winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); + int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + m_window = m_framework->open_window(winProps, flags); + m_window->set_background_type(WindowFramework::BackgroundType::BT_black); + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->initFromParent(m_framework, m_window); + } + } + + void setupScene() vp_override { } + + + void setCameraPose(const vpHomogeneousMatrix &wTc) vp_override + { + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setCameraPose(wTc); + } + } + + vpHomogeneousMatrix getCameraPose() vp_override + { + if (m_subRenderers.size() == 0) { + throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); + } + return m_subRenderers[0]->getCameraPose(); + } + + void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) vp_override + { + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setNodePose(name, wTo); + } + } + + void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) vp_override + { + throw vpException(vpException::badValue, "NodePath setNodePose is not supported in renderer set, prefer the string version"); + } + vpHomogeneousMatrix getNodePose(const std::string &name) vp_override + { + if (m_subRenderers.size() == 0) { + throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); + } + return m_subRenderers[0]->getNodePose(name); + } + vpHomogeneousMatrix getNodePose(NodePath &object) vp_override + { + throw vpException(vpException::badValue, "NodePath getNodePose is not supported in renderer set, prefer the string version"); + } + + + void addNodeToScene(const NodePath &object) vp_override + { + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->addNodeToScene(object); + } + } + + + void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override + { + m_renderParameters = params; + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setRenderParameters(m_renderParameters); + } + } + + void addSubRenderer(std::shared_ptr renderer) + { + for (std::shared_ptr &otherRenderer: m_subRenderers) { + if (renderer->getName() == otherRenderer->getName()) { + throw vpException(vpException::badValue, "Cannot have two subrenderers with the same name"); + } + } + m_subRenderers.push_back(renderer); + if (m_framework != nullptr) { + renderer->setRenderParameters(m_renderParameters); + renderer->initFromParent(m_framework, m_window); + renderer->setCameraPose(getCameraPose()); + } + } + + template + std::shared_ptr getFirstMatch() + { + for (std::shared_ptr &renderer: m_subRenderers) { + std::shared_ptr rendererCast = std::dynamic_pointer_cast(renderer); + if (rendererCast != nullptr) { + return rendererCast; + } + } + return nullptr; + } + + std::shared_ptr getRenderer(const std::string &name) + { + for (std::shared_ptr &renderer: m_subRenderers) { + if (renderer->getName() == name) { + return renderer; + } + } + return nullptr; + } + + + + + +private: + std::vector> m_subRenderers; +}; + +#endif +#endif diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 91d86b01b3..afb6e70278 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -5,13 +5,19 @@ #include #include #include +#include + #include int main() { vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.001, 10.0); - vpPanda3DBaseRenderer renderer("basic"); + vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); + + std::shared_ptr base = std::shared_ptr(new vpPanda3DBaseRenderer("base")); + renderer.addSubRenderer(base); + std::cout << "Initializing framework" << std::endl; renderer.initFramework(true); std::cout << "Loading object" << std::endl; NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); From 740203d0cfed20572299279aec48b4d9540c76fc Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 17 Apr 2024 16:51:17 +0200 Subject: [PATCH 07/65] debugging lens projection matrix --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 44 +++-- .../include/visp3/ar/vpPanda3DRendererSet.h | 3 + tutorial/ar/data/simple_object.mtl | 10 ++ tutorial/ar/data/simple_object.obj | 164 ++++++++++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 30 ++-- 5 files changed, 224 insertions(+), 27 deletions(-) create mode 100644 tutorial/ar/data/simple_object.mtl create mode 100644 tutorial/ar/data/simple_object.obj diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 1df6254df1..59e4b61906 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -74,29 +74,34 @@ class VISP_EXPORT vpPanda3DRenderParameters // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl - PT(MatrixLens) lens = new MatrixLens(); - lens->set_near_far(m_clipNear, m_clipFar); - const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); - const double B = -2.0 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); - - const double cx = m_cam.get_u0(); - const double cy = m_height - m_cam.get_v0(); - lens->set_film_size(m_width, m_height); - lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); - lens->set_user_mat(LMatrix4( - m_cam.get_px(), 0, 0, 0, - 0, 0, A, 1, - 0, m_cam.get_py(), 0, 0, - 0, 0, B, 0 - )); + if (lens == nullptr) { + lens = new MatrixLens(); + const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); + const double B = -2.0 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); + + const double cx = m_cam.get_u0(); + const double cy = m_height - m_cam.get_v0(); + lens->set_film_size(m_width, m_height); + lens->set_near_far(m_clipNear, m_clipFar); + lens->set_user_mat(LMatrix4( + m_cam.get_px(), 0, 0, 0, + 0, 0, A, 1, + 0, m_cam.get_py(), 0, 0, + 0, 0, B, 0 + )); + lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); + std::cout << lens->get_aspect_ratio() << std::endl; + } camera->set_lens(lens); + camera->set_lens_active(0, true); } private: vpCameraParameters m_cam; unsigned int m_height, m_width; double m_clipNear, m_clipFar; + PT(MatrixLens) lens; }; @@ -130,6 +135,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_renderRoot.set_shader_auto(); } void initFromParent(std::shared_ptr framework, PT(WindowFramework) window) @@ -138,6 +144,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer m_window = window; setupScene(); setupCamera(); + m_window->get_display_region_3d()->set_camera(m_cameraPath); } virtual void renderFrame() @@ -151,7 +158,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void setCameraPose(const vpHomogeneousMatrix &wTc) { - if (m_camera.is_null()) { + if (m_camera.is_null() || m_cameraPath.is_empty()) { throw vpException(vpException::notInitialized, "Camera was not initialized before trying to set its pose"); } setNodePose(m_cameraPath, wTc); @@ -185,6 +192,9 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual vpHomogeneousMatrix getNodePose(const std::string &name) { NodePath object = m_renderRoot.find(name); + if (object.is_empty()) { + throw vpException(vpException::badValue, "Node %s was not found", name); + } return getNodePose(object); } virtual vpHomogeneousMatrix getNodePose(NodePath &object) @@ -222,7 +232,9 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void addNodeToScene(const NodePath &object) { NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); + objectInScene.set_shader_auto(); objectInScene.set_name(object.get_name()); + std::cout << objectInScene.get_mat() << std::endl; } diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index d95c6ce4ff..261b5c96e3 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -28,6 +28,9 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; m_window = m_framework->open_window(winProps, flags); + if (m_window == nullptr) { + throw vpException(vpException::fatalError, "Could not open Panda3D window (hidden or visible)"); + } m_window->set_background_type(WindowFramework::BackgroundType::BT_black); for (std::shared_ptr &renderer: m_subRenderers) { renderer->initFromParent(m_framework, m_window); diff --git a/tutorial/ar/data/simple_object.mtl b/tutorial/ar/data/simple_object.mtl new file mode 100644 index 0000000000..34c0586781 --- /dev/null +++ b/tutorial/ar/data/simple_object.mtl @@ -0,0 +1,10 @@ +# Blender 3.4.1 MTL File: 'None' +# www.blender.org + +newmtl Material +Kd 0.800000 0.000000 0.001367 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 3 diff --git a/tutorial/ar/data/simple_object.obj b/tutorial/ar/data/simple_object.obj new file mode 100644 index 0000000000..5d75c7b2bb --- /dev/null +++ b/tutorial/ar/data/simple_object.obj @@ -0,0 +1,164 @@ +# Blender 3.4.1 +# www.blender.org +mtllib simple_object.mtl +o Cube +v 0.473170 0.738021 -0.738021 +v 0.473170 -0.738021 -0.738021 +v 0.473170 0.738021 0.738021 +v 0.473170 -0.738021 0.738021 +v -0.473170 0.738021 -0.738021 +v -0.473170 -0.738021 -0.738021 +v -0.473170 0.738021 0.738021 +v -0.473170 -0.738021 0.738021 +v -0.473170 -0.738021 0.427758 +v 0.198234 0.525310 0.188069 +v -0.198234 0.650917 0.188069 +v 0.473170 -0.738021 0.427758 +v 0.473170 1.111521 0.738021 +v -0.473170 1.111521 0.738021 +v 0.473170 1.111521 0.427758 +v -0.473170 1.111521 0.427758 +v -0.198234 0.650917 -0.157573 +v 0.473170 -0.738021 -0.397262 +v -0.473170 -0.738021 -0.397262 +v 0.198234 0.525310 -0.157573 +v -0.473170 1.248340 -0.397262 +v -0.473170 1.248340 -0.738021 +v 0.473170 1.248340 -0.738021 +v 0.473170 1.248340 -0.397262 +v 0.769975 0.438207 -0.397262 +v 0.769975 0.438207 0.427758 +v 0.769975 -0.738021 0.427758 +v 0.769975 -0.738021 -0.397262 +v 0.319294 -0.028908 -0.377806 +v -0.170810 -0.028908 0.386627 +v 0.319294 -0.028908 0.386627 +v -0.170810 -0.028908 -0.377806 +v 0.248100 -0.083989 0.163877 +v -0.099616 -0.051464 0.163877 +v 0.248100 -0.083989 -0.139262 +v -0.099616 -0.051464 -0.139262 +v 0.473008 -0.106544 0.225944 +v 0.473008 -0.106544 -0.201329 +vn -0.9338 -0.2287 -0.2753 +vn -0.0000 -0.4165 0.9091 +vn -0.9172 -0.1558 0.3668 +vn -0.0000 -1.0000 -0.0000 +vn 0.9055 0.0467 -0.4218 +vn -0.0000 -0.4251 -0.9051 +vn -0.3640 -0.0941 -0.9267 +vn -0.9101 -0.1496 -0.3864 +vn 0.3020 0.9533 -0.0000 +vn -0.0000 1.0000 -0.0000 +vn 0.0389 0.4160 -0.9085 +vn -0.0000 -0.0000 1.0000 +vn 0.9454 -0.2082 -0.2507 +vn -0.0000 -0.0000 -1.0000 +vn 0.9155 0.0409 0.4003 +vn -0.9903 -0.1390 -0.0000 +vn 0.0319 0.3411 0.9395 +vn -0.9475 -0.1776 0.2660 +vn 0.9549 -0.1649 0.2469 +vn 0.8780 -0.4786 -0.0000 +vn 0.1506 0.9886 -0.0000 +vn -0.0997 0.3029 0.9478 +vn -0.3647 -0.1031 0.9254 +vn 0.9049 0.4256 -0.0000 +vn -0.8784 0.4779 -0.0000 +vn 0.9686 0.2064 0.1387 +vn -0.9139 0.3817 -0.1382 +vn -0.1003 0.2948 -0.9503 +vn -0.0000 0.4529 -0.8916 +vn 0.9680 0.2013 -0.1501 +vn -0.9136 0.3861 0.1273 +vn -0.0000 0.4440 0.8960 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.250000 +vt 0.625000 0.000000 +vt 0.625000 1.000000 +vt 0.125000 0.750000 +vt 0.375000 0.000000 +vt 0.375000 1.000000 +vt 0.125000 0.697450 +vt 0.375000 0.052550 +vt 0.670572 0.677152 +vt 0.802369 0.656853 +vt 0.615165 0.093146 +vt 0.375000 0.697450 +vt 0.625000 0.750000 +vt 0.625000 0.000000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 0.697450 +vt 0.625000 0.052550 +vt 0.875000 0.697450 +vt 0.802369 0.598312 +vt 0.615165 0.151688 +vt 0.375000 0.557715 +vt 0.125000 0.557715 +vt 0.375000 0.192285 +vt 0.670572 0.578013 +vt 0.875000 0.557715 +vt 0.625000 0.192285 +vt 0.875000 0.500000 +vt 0.625000 0.250000 +vt 0.625000 0.500000 +vt 0.625000 0.557715 +vt 0.625000 0.557715 +vt 0.625000 0.697450 +vt 0.375000 0.697450 +vt 0.375000 0.557715 +vt 0.532627 0.557034 +vt 0.501436 0.083267 +vt 0.495104 0.920124 +vt 0.533904 0.695375 +vt 0.501624 0.165036 +vt 0.533436 0.683847 +vt 0.511781 0.097244 +vt 0.533329 0.571291 +vt 0.511781 0.148587 +vt 0.520549 0.690019 +vt 0.520451 0.565125 +s 0 +usemtl Material +f 11/17/1 7/8/1 14/20/1 16/24/1 +f 31/45/2 3/3/2 7/9/2 30/44/2 +f 36/50/3 17/27/3 5/5/3 32/46/3 +f 9/13/4 12/18/4 4/4/4 8/10/4 +f 33/47/5 10/15/5 3/3/5 31/45/5 +f 32/46/6 5/5/6 1/1/6 29/42/6 +f 35/49/7 20/31/7 25/38/7 38/52/7 +f 19/29/4 18/28/4 12/18/4 9/13/4 +f 30/43/8 7/8/8 11/17/8 34/48/8 +f 20/31/9 17/26/9 11/16/9 10/15/9 +f 15/23/10 16/25/10 14/21/10 13/19/10 +f 10/15/11 11/16/11 16/25/11 15/23/11 +f 7/9/12 3/3/12 13/19/12 14/22/12 +f 3/3/13 10/15/13 15/23/13 13/19/13 +f 1/1/14 5/5/14 22/35/14 23/36/14 +f 6/6/4 2/2/4 18/28/4 19/29/4 +f 29/42/15 1/1/15 20/31/15 35/49/15 +f 34/48/16 11/17/16 17/27/16 36/50/16 +f 23/36/10 22/34/10 21/32/10 24/37/10 +f 17/26/17 20/31/17 24/37/17 21/32/17 +f 5/5/18 17/27/18 21/33/18 22/35/18 +f 20/31/19 1/1/19 23/36/19 24/37/19 +f 38/52/20 25/38/20 26/39/20 37/51/20 +f 20/31/21 10/15/21 26/39/21 25/38/21 +f 12/18/4 18/28/4 28/41/4 27/40/4 +f 33/47/22 12/18/22 27/40/22 37/51/22 +f 10/15/23 33/47/23 37/51/23 26/39/23 +f 28/41/24 38/52/24 37/51/24 27/40/24 +f 9/14/25 34/48/25 36/50/25 19/30/25 +f 2/2/26 29/42/26 35/49/26 18/28/26 +f 8/11/27 30/43/27 34/48/27 9/14/27 +f 18/28/28 35/49/28 38/52/28 28/41/28 +f 6/7/29 32/46/29 29/42/29 2/2/29 +f 12/18/30 33/47/30 31/45/30 4/4/30 +f 19/30/31 36/50/31 32/46/31 6/7/31 +f 4/4/32 31/45/32 30/44/32 8/12/32 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index afb6e70278..73e96664bd 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -6,38 +6,46 @@ #include #include #include - +#include "load_prc_file.h" #include int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.001, 10.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 5.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); + load_prc_file_data("", "sync-video false"); + load_prc_file_data("", "assert-abort 1"); std::shared_ptr base = std::shared_ptr(new vpPanda3DBaseRenderer("base")); renderer.addSubRenderer(base); std::cout << "Initializing framework" << std::endl; renderer.initFramework(true); + std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); + NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_object.obj"); std::cout << "Adding node to scene" <set_color(LColor(0.2, 0.2, 0.2, 1.0)); - // NodePath alnp = renderer.getRenderRoot().attach_new_node(alight); - // renderer.getRenderRoot().set_light(alnp); + + PT(AmbientLight) alight = new AmbientLight("alight"); + alight->set_color(LColor(0.2, 0.2, 0.2, 1.0)); + NodePath alnp = base->getRenderRoot().attach_new_node(alight); + base->getRenderRoot().set_light(alnp); std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); + //renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); while (true) { const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); const double afterRender = vpTime::measureTimeMs(); const double delta = (afterRender - beforeRender) / 1000.0; - vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); - vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); - renderer.setNodePose("cube", wTo * oToo); + // std::cout << "Before get pose" << std::endl; + // vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); + // std::cout << wTo << std::endl; + // vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + // renderer.setNodePose("cube", wTo * oToo); + // std::cout << object.get_scale() << std::endl; + // std::cout << "After set pose " << std::endl; std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; } return 0; From 8e482840e3c6dd8e7ab058d0ebd1536f7f98b7b9 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 17 Apr 2024 23:55:57 +0200 Subject: [PATCH 08/65] normal and depth render working --- modules/ar/CMakeLists.txt | 4 + .../include/visp3/ar/vpPanda3DBaseRenderer.h | 39 +++++--- .../visp3/ar/vpPanda3DGeometryRenderer.h | 40 ++++++++ .../include/visp3/ar/vpPanda3DRendererSet.h | 18 ++-- .../vpPanda3DGeometryRenderer.cpp | 94 +++++++++++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 72 ++++++++++---- 6 files changed, 228 insertions(+), 39 deletions(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index cad171d91d..8d2662d3a3 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -190,5 +190,9 @@ if(USE_OGRE) vp_set_source_file_compile_flag(src/ogre-simulator/vpAROgre.cpp -Wno-unused-parameter -Wno-unused-but-set-parameter -Wno-overloaded-virtual -Wno-float-equal -Wno-deprecated-copy -Wno-register) endif() +if(USE_PANDA3D) + vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp -Wno-unused-parameter -Wno-unused-variable -Wno-extra) +endif() + vp_module_include_directories(${opt_incs}) vp_create_module(${opt_libs}) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 59e4b61906..fb2d1d4d97 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -73,7 +73,7 @@ class VISP_EXPORT vpPanda3DRenderParameters // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl - + std::cout << "Calling setup panda camera" << std::endl; if (lens == nullptr) { lens = new MatrixLens(); const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); @@ -81,7 +81,7 @@ class VISP_EXPORT vpPanda3DRenderParameters const double cx = m_cam.get_u0(); const double cy = m_height - m_cam.get_v0(); - lens->set_film_size(m_width, m_height); + lens->set_near_far(m_clipNear, m_clipFar); lens->set_user_mat(LMatrix4( m_cam.get_px(), 0, 0, 0, @@ -89,12 +89,12 @@ class VISP_EXPORT vpPanda3DRenderParameters 0, m_cam.get_py(), 0, 0, 0, 0, B, 0 )); + lens->set_film_size(m_width, m_height); lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); - std::cout << lens->get_aspect_ratio() << std::endl; } camera->set_lens(lens); - camera->set_lens_active(0, true); + // camera->set_lens_active(0, true); } private: @@ -115,6 +115,8 @@ class VISP_EXPORT vpPanda3DBaseRenderer public: vpPanda3DBaseRenderer(const std::string &rendererName) : m_name(rendererName), m_framework(nullptr), m_window(nullptr) { } + virtual ~vpPanda3DBaseRenderer() = default; + virtual void initFramework(bool showWindow) { if (m_framework.use_count() > 0) { @@ -132,11 +134,6 @@ class VISP_EXPORT vpPanda3DBaseRenderer m_window->get_display_region_3d()->set_camera(m_cameraPath); } - virtual void setupScene() - { - m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_renderRoot.set_shader_auto(); - } void initFromParent(std::shared_ptr framework, PT(WindowFramework) window) { @@ -144,12 +141,23 @@ class VISP_EXPORT vpPanda3DBaseRenderer m_window = window; setupScene(); setupCamera(); + setupRenderTarget(); m_window->get_display_region_3d()->set_camera(m_cameraPath); } + virtual void setupScene() + { + m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_renderRoot.set_shader_auto(); + } + + virtual void setupRenderTarget() { } + virtual void renderFrame() { m_framework->get_graphics_engine()->render_frame(); + m_framework->get_graphics_engine()->sync_frame(); + } @@ -213,18 +221,19 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ virtual void setupCamera() { - m_cameraPath = m_window->make_camera(); - m_camera = (Camera *)m_cameraPath.node(); - // m_camera = m_window->get_camera(0); + + m_camera = m_window->get_camera(0); + //m_camera = (Camera *)m_cameraPath.node(); + // m_camera = m_window->get_camera(0); m_cameraPath = m_renderRoot.attach_new_node(m_camera); m_renderParameters.setupPandaCamera(m_camera); } NodePath loadObject(const std::string &nodeName, const std::string &modelPath) { - NodePath model = m_window->load_model(m_window->get_render(), modelPath); + NodePath model = m_window->load_model(m_framework->get_models(), modelPath); std::cout << "After loading model" << std::endl; - //model.detach_node(); + model.detach_node(); model.set_name(nodeName); return model; } @@ -232,7 +241,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void addNodeToScene(const NodePath &object) { NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); - objectInScene.set_shader_auto(); + //objectInScene.set_shader_auto(); objectInScene.set_name(object.get_name()); std::cout << objectInScene.get_mat() << std::endl; } diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h new file mode 100644 index 0000000000..9df86661bd --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -0,0 +1,40 @@ +#ifndef vpPanda3DGeometryRenderer_h +#define vpPanda3DGeometryRenderer_h + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include +#include +#include + +/** + * @brief Renderer that outputs object normals (in world frame) as well as depth + * + */ +class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer +{ +public: + vpPanda3DGeometryRenderer(const std::string &rendererName) : vpPanda3DBaseRenderer(rendererName) { } + ~vpPanda3DGeometryRenderer() { } + + void setupScene() vp_override; + void setupRenderTarget() vp_override; + + + void getRender(vpImage &normals, vpImage &depth) const; + + + static const char *SHADER_VERT_NORMAL_AND_DEPTH; + static const char *SHADER_FRAG_NORMAL_AND_DEPTH; + +private: + PT(Shader) m_normalDepthShader; + Texture *m_normalDepthTexture; + GraphicsOutput *m_normalDepthBuffer; + +}; + + +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index 261b5c96e3..f5acd594e2 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -39,6 +39,8 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer void setupScene() vp_override { } + void setupCamera() vp_override { } + void setCameraPose(const vpHomogeneousMatrix &wTc) vp_override { @@ -103,15 +105,15 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer } } m_subRenderers.push_back(renderer); + renderer->setRenderParameters(m_renderParameters); if (m_framework != nullptr) { - renderer->setRenderParameters(m_renderParameters); renderer->initFromParent(m_framework, m_window); renderer->setCameraPose(getCameraPose()); } } template - std::shared_ptr getFirstMatch() + std::shared_ptr getRenderer() { for (std::shared_ptr &renderer: m_subRenderers) { std::shared_ptr rendererCast = std::dynamic_pointer_cast(renderer); @@ -122,20 +124,20 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer return nullptr; } - std::shared_ptr getRenderer(const std::string &name) + template + std::shared_ptr getRenderer(const std::string &name) { for (std::shared_ptr &renderer: m_subRenderers) { if (renderer->getName() == name) { - return renderer; + std::shared_ptr rendererCast = std::dynamic_pointer_cast(renderer); + if (rendererCast != nullptr) { + return rendererCast; + } } } return nullptr; } - - - - private: std::vector> m_subRenderers; }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp new file mode 100644 index 0000000000..80fb8d80c7 --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -0,0 +1,94 @@ +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include + + +const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH = R"shader( +#version 120 + +varying vec3 oNormal; + +void main() +{ + gl_Position = ftransform(); + oNormal = normalize(gl_Normal); +} +)shader"; + +const char *vpPanda3DGeometryRenderer::SHADER_FRAG_NORMAL_AND_DEPTH = R"shader( +#version 120 + +varying vec3 oNormal; + +void main() +{ + vec3 n = normalize(oNormal); + //if (!gl_FrontFacing) + //n = -n; + float fDepth = gl_FragCoord.z; + + gl_FragColor = vec4(n, fDepth); +} +)shader"; + + +void vpPanda3DGeometryRenderer::setupScene() +{ + m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_normalDepthShader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + SHADER_VERT_NORMAL_AND_DEPTH, + SHADER_FRAG_NORMAL_AND_DEPTH); + m_renderRoot.set_shader(m_normalDepthShader); + + +} + + +void vpPanda3DGeometryRenderer::setupRenderTarget() +{ + FrameBufferProperties fbp; + fbp.set_rgb_color(true); + fbp.set_float_depth(false); + fbp.set_float_color(true); + fbp.set_depth_bits(16); + fbp.set_rgba_bits(32, 32, 32, 32); + + WindowProperties win_prop; + win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); + + // Don't open a window - force it to be an offscreen buffer. + int flags = GraphicsPipe::BF_refuse_window; + + GraphicsEngine *engine = m_window->get_graphics_output()->get_engine(); + GraphicsPipe *pipe = m_window->get_graphics_output()->get_pipe(); + m_normalDepthBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, + m_window->get_graphics_output()->get_gsg(), + m_window->get_graphics_output()); + m_normalDepthTexture = new Texture(); + fbp.setup_color_texture(m_normalDepthTexture); + m_normalDepthBuffer->add_render_texture(m_normalDepthTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); + m_normalDepthBuffer->set_clear_color(LColor(0.f)); + m_normalDepthBuffer->set_clear_color_active(true); + DisplayRegion *region = m_normalDepthBuffer->make_display_region(); + region->set_camera(m_cameraPath); + region->set_clear_color(LColor(0.f)); +} + +void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImage &depth) const +{ + normals.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + +#pragma omp parallel for simd + for (unsigned int i = 0; i < normals.getRows() * normals.getCols(); ++i) { + normals.bitmap[i].R = (data[i * 4]); + normals.bitmap[i].G = (data[i * 4 + 1]); + normals.bitmap[i].B = (data[i * 4 + 2]); + depth.bitmap[i] = (data[i * 4 + 3]); + } +} + +#endif diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 73e96664bd..b179a7c52d 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -6,21 +6,39 @@ #include #include #include +#include #include "load_prc_file.h" #include +#include + +void convertToDisplay(const vpImage &normalsImage, const vpImage &depthImage, + vpImage &normalDisplayImage, vpImage &depthDisplayImage, float minDepth, float maxDepth) +{ + std::cout << depthImage[0][0] << std::endl; +#pragma omp parallel for simd + for (unsigned int i = 0; i < normalsImage.getSize(); ++i) { + normalDisplayImage.bitmap[i].R = static_cast((normalsImage.bitmap[i].R + 1.0) * 127.5f); + normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); + normalDisplayImage.bitmap[i].B = static_cast((normalsImage.bitmap[i].B + 1.0) * 127.5f); + depthDisplayImage.bitmap[i] = static_cast(((depthImage.bitmap[i] - minDepth) / (maxDepth - minDepth)) * 255.f); + } +} + + int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 5.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); load_prc_file_data("", "sync-video false"); load_prc_file_data("", "assert-abort 1"); - std::shared_ptr base = std::shared_ptr(new vpPanda3DBaseRenderer("base")); - renderer.addSubRenderer(base); + std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer("geom")); + + renderer.addSubRenderer(geometryRenderer); std::cout << "Initializing framework" << std::endl; - renderer.initFramework(true); + renderer.initFramework(false); std::cout << "Loading object" << std::endl; NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_object.obj"); @@ -28,24 +46,46 @@ int main() renderer.addNodeToScene(object); - PT(AmbientLight) alight = new AmbientLight("alight"); - alight->set_color(LColor(0.2, 0.2, 0.2, 1.0)); - NodePath alnp = base->getRenderRoot().attach_new_node(alight); - base->getRenderRoot().set_light(alnp); + // PT(AmbientLight) alight = new AmbientLight("alight"); + // alight->set_color(LColor(0.2, 0.2, 0.2, 1.0)); + // NodePath alnp = base->getRenderRoot().attach_new_node(alight); + // base->getRenderRoot().set_light(alnp); std::cout << "Setting camera pose" << std::endl; - //renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); + + vpImage normalsImage; + vpImage depthImage; + vpImage normalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); + vpImage depthDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); + + vpDisplayX dNormals(normalDisplayImage, 0, 0, "normals"); + vpDisplayX dDepth(depthDisplayImage, renderParams.getImageWidth(), 0, "depth"); + + + while (true) { const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); + renderer.getRenderer()->getRender(normalsImage, depthImage); + + convertToDisplay(normalsImage, depthImage, normalDisplayImage, depthDisplayImage, 4, 8.0); + const double afterRender = vpTime::measureTimeMs(); const double delta = (afterRender - beforeRender) / 1000.0; - // std::cout << "Before get pose" << std::endl; - // vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); - // std::cout << wTo << std::endl; - // vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); - // renderer.setNodePose("cube", wTo * oToo); - // std::cout << object.get_scale() << std::endl; - // std::cout << "After set pose " << std::endl; + vpDisplay::display(normalDisplayImage); + vpDisplay::flush(normalDisplayImage); + + vpDisplay::display(depthDisplayImage); + vpDisplay::flush(depthDisplayImage); + + + std::cout << "Before get pose" << std::endl; + vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); + std::cout << wTo << std::endl; + vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + renderer.setNodePose("cube", wTo * oToo); + std::cout << object.get_scale() << std::endl; + std::cout << "After set pose " << std::endl; std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; } return 0; From 1201841cf09b93721099883fef9409421d585916 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Thu, 18 Apr 2024 17:31:30 +0200 Subject: [PATCH 09/65] Normal rendering, workin on RGB rendering --- modules/ar/CMakeLists.txt | 8 +- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 306 ++++++------------ .../include/visp3/ar/vpPanda3DRGBRenderer.h | 69 ++++ .../visp3/ar/vpPanda3DRenderParameters.h | 99 ++++++ .../vpPanda3DBaseRenderer.cpp | 128 ++++++++ .../vpPanda3DRGBRenderer.cpp | 43 +++ .../vpPanda3DRenderParameters.cpp | 38 +++ tutorial/ar/tutorial-panda3d-renderer.cpp | 50 ++- 8 files changed, 523 insertions(+), 218 deletions(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h create mode 100644 modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 8d2662d3a3..56dbca39eb 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -191,7 +191,13 @@ if(USE_OGRE) endif() if(USE_PANDA3D) - vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp -Wno-unused-parameter -Wno-unused-variable -Wno-extra) + set(PANDA3D_CXX_FLAGS -Wno-unused-parameter -Wno-unused-variable -Wno-extra) + vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp ${PANDA3D_CXX_FLAGS}) + vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DRGBRenderer.cpp ${PANDA3D_CXX_FLAGS}) + vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DBaseRenderer.cpp ${PANDA3D_CXX_FLAGS}) + vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DRenderParameters.cpp ${PANDA3D_CXX_FLAGS}) + + endif() vp_module_include_directories(${opt_incs}) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index fb2d1d4d97..8316125d5f 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -5,110 +5,21 @@ #if defined(VISP_HAVE_PANDA3D) #include +#include #include #include -#include - - - -/** - * @brief Rendering parameters for a panda3D simulation - * - * includes: - * - Camera intrinsics - * - Image resolution - * - Clipping parameters - */ -class VISP_EXPORT vpPanda3DRenderParameters -{ -public: - vpPanda3DRenderParameters() : m_cam(), m_height(0), m_width(0), m_clipNear(0.001), m_clipFar(10.0) { } - vpPanda3DRenderParameters(const vpCameraParameters &cam, unsigned int h, unsigned int w, - double clipNear, double clipFar) - : m_cam(cam), m_height(h), m_width(w), m_clipNear(clipNear), m_clipFar(clipFar) - { } - - /** - * @brief Retrieve camera intrinsics. - * - * @return const vpCameraParameters& - */ - const vpCameraParameters &getCameraIntrinsics() const { return m_cam; } - /** - * @brief set camera intrinsics. Only camera intrinsics for a lens without distortion are supported. - * \throws if camera intrinsics have a distortion model. - */ - void setCameraIntrinsics(const vpCameraParameters &cam) - { - if (cam.get_projModel() != vpCameraParameters::perspectiveProjWithoutDistortion) { - throw vpException(vpException::badValue, "Panda3D renderer: only lenses with no distortion are supported"); - } - m_cam = cam; - } - - double getNearClippingDistance() const { return m_clipNear; } - double getFarClippingDistance() const { return m_clipFar; } - - void setClippingDistance(double near, double far) - { - if (far < near) { - std::swap(near, far); - } - m_clipNear = near; - m_clipFar = far; - } - - unsigned int getImageWidth() const { return m_width; } - unsigned int getImageHeight() const { return m_height; } - - void setImageResolution(unsigned int height, unsigned int width) - { - m_height = height; - m_width = width; - } - - void setupPandaCamera(Camera *camera) - { - // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), - // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 - // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl - std::cout << "Calling setup panda camera" << std::endl; - if (lens == nullptr) { - lens = new MatrixLens(); - const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); - const double B = -2.0 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); - - const double cx = m_cam.get_u0(); - const double cy = m_height - m_cam.get_v0(); - - lens->set_near_far(m_clipNear, m_clipFar); - lens->set_user_mat(LMatrix4( - m_cam.get_px(), 0, 0, 0, - 0, 0, A, 1, - 0, m_cam.get_py(), 0, 0, - 0, 0, B, 0 - )); - lens->set_film_size(m_width, m_height); - lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); - } - - camera->set_lens(lens); - // camera->set_lens_active(0, true); - } - -private: - vpCameraParameters m_cam; - unsigned int m_height, m_width; - double m_clipNear, m_clipFar; - PT(MatrixLens) lens; -}; /** * @brief Base class for a panda3D renderer. This class handles basic functionalities, - * such as loading object, changing camera parameters etc. + * such as loading object, changing camera parameters. * + * For a subclass to have a novel behaviour (e.g, display something else) These methods should be overriden: + * + * - setupScene: This is where you should apply your shaders. + * - setupCamera: This is where cameras are created and intrinsics parameters are applied + * - setupRenderTarget: This is where you should create the texture buffers, where the render results should be stored. */ class VISP_EXPORT vpPanda3DBaseRenderer { @@ -117,138 +28,123 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual ~vpPanda3DBaseRenderer() = default; - virtual void initFramework(bool showWindow) - { - if (m_framework.use_count() > 0) { - throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); - } - m_framework = std::shared_ptr(new PandaFramework()); - m_framework->open_framework(); - WindowProperties winProps; - winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); - int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; - m_window = m_framework->open_window(winProps, flags); - m_window->set_background_type(WindowFramework::BackgroundType::BT_black); - setupScene(); - setupCamera(); - m_window->get_display_region_3d()->set_camera(m_cameraPath); - } + /** + * @brief Initialize the whole Panda3D framework. Create a new PandaFramework object and a new window. + * + * Will also perform the renderer setup (scene, camera and render targets) + * + * @param showWindow whether the created window should be visible + */ + virtual void initFramework(bool showWindow); + /** + * @brief + * + * @param framework + */ + void initFromParent(std::shared_ptr framework, PT(WindowFramework) window); - void initFromParent(std::shared_ptr framework, PT(WindowFramework) window) - { - m_framework = framework; - m_window = window; - setupScene(); - setupCamera(); - setupRenderTarget(); - m_window->get_display_region_3d()->set_camera(m_cameraPath); - } + /** + * @brief Initialize the scene for this specific renderer. + * + * Creates a root scene for this node and applies shaders. that will be used for rendering + * + */ + virtual void setupScene(); - virtual void setupScene() - { - m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_renderRoot.set_shader_auto(); - } + /** + * @brief Initialize camera. Should be called when the scene root of this render has already been created. + * + */ + virtual void setupCamera(); virtual void setupRenderTarget() { } - virtual void renderFrame() - { - m_framework->get_graphics_engine()->render_frame(); - m_framework->get_graphics_engine()->sync_frame(); - - } - + virtual void renderFrame(); + /** + * @brief Get the name of the renderer + * + * @return const std::string& + */ const std::string &getName() const { return m_name; } - NodePath getRenderRoot() { return m_renderRoot; } - - virtual void setCameraPose(const vpHomogeneousMatrix &wTc) - { - if (m_camera.is_null() || m_cameraPath.is_empty()) { - throw vpException(vpException::notInitialized, "Camera was not initialized before trying to set its pose"); - } - setNodePose(m_cameraPath, wTc); - } - virtual vpHomogeneousMatrix getCameraPose() - { - if (m_camera.is_null()) { - throw vpException(vpException::notInitialized, "Camera was not initialized before trying to get its pose"); - } - return getNodePose(m_cameraPath); - } /** - * @brief Set the pose of a node. + * @brief Get the scene root * - * @param name Node path to search for, from the render root. See https://docs.panda3d.org/1.10/python/programming/scene-graph/searching-scene-graph - * @param wTo Pose of the object in the world frame */ - virtual void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) - { - NodePath object = m_renderRoot.find(name); - setNodePose(object, wTo); - } + NodePath &getRenderRoot() { return m_renderRoot; } - virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) - { - vpTranslationVector t = wTo.getTranslationVector(); - vpQuaternionVector q(wTo.getRotationMatrix()); - object.set_pos(t[0], t[1], t[2]); - object.set_quat(LQuaternion(q.w(), q.x(), q.y(), q.z())); - } - virtual vpHomogeneousMatrix getNodePose(const std::string &name) + /** + * @brief Set new rendering parameters. If the scene has already been initialized, the renderer camera is updated. + * + * @param params the new rendering parameters + */ + virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms) { - NodePath object = m_renderRoot.find(name); - if (object.is_empty()) { - throw vpException(vpException::badValue, "Node %s was not found", name); + m_renderParameters = params; + if (m_camera != nullptr) { + m_renderParameters.setupPandaCamera(m_camera); } - return getNodePose(object); - } - virtual vpHomogeneousMatrix getNodePose(NodePath &object) - { - const LPoint3 pos = object.get_pos(); - const LQuaternion quat = object.get_quat(); - const vpTranslationVector t(pos[0], pos[1], pos[2]); - const vpQuaternionVector q(quat.get_i(), quat.get_j(), quat.get_k(), quat.get_r()); - return vpHomogeneousMatrix(t, q); } - /** - * @brief Initialize camera. Should be called when the scene root of this render has already been created. + * @brief Set the camera's pose. * + * @param wTc the new pose of the camera, in world frame */ - virtual void setupCamera() - { - - m_camera = m_window->get_camera(0); - //m_camera = (Camera *)m_cameraPath.node(); - // m_camera = m_window->get_camera(0); - m_cameraPath = m_renderRoot.attach_new_node(m_camera); - m_renderParameters.setupPandaCamera(m_camera); - } + virtual void setCameraPose(const vpHomogeneousMatrix &wTc); - NodePath loadObject(const std::string &nodeName, const std::string &modelPath) - { - NodePath model = m_window->load_model(m_framework->get_models(), modelPath); - std::cout << "After loading model" << std::endl; - model.detach_node(); - model.set_name(nodeName); - return model; - } - - virtual void addNodeToScene(const NodePath &object) - { - NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); - //objectInScene.set_shader_auto(); - objectInScene.set_name(object.get_name()); - std::cout << objectInScene.get_mat() << std::endl; - } + /** + * @brief Retrieve the camera's pose, in the world frame. + */ + virtual vpHomogeneousMatrix getCameraPose(); + /** + * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). + * + * @param name Node path to search for, from the render root. This is the object that will be modified See https://docs.panda3d.org/1.10/python/programming/scene-graph/searching-scene-graph + * @param wTo Pose of the object in the world frame + * + * \throws if the corresponding node cannot be found. + */ + virtual void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo); + /** + * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). + * + * @param object The object for which to set the pose + * @param wTo Pose of the object in the world frame + */ + virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo); + /** + * @brief Get the pose of a Panda node, in world frame. + * + * @param name Node path to search for. \see setNodePose(const std::string &, const vpHomogeneousMatrix &) for more info + * @return wTo, the pose of the object in world frame + * \throws if no node can be found from the given path. + */ + virtual vpHomogeneousMatrix getNodePose(const std::string &name); + /** + * @brief Get the pose of a Panda node, in world frame. This version of the method directly uses the Panda Nodepath. + */ + virtual vpHomogeneousMatrix getNodePose(NodePath &object); - virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms) { m_renderParameters = params; } + /** + * @brief Load a 3D object. To load an .obj file, Panda3D must be compiled with assimp support. + * + * Once loaded, the object will not be visible, it should be added to the scene. + * + * @param nodeName the name that will be used when inserting the node in the scene graph + * @param modelPath Path to the model file + * @return NodePath The NodePath containing the 3D model, which can now be added to the scene graph. + */ + NodePath loadObject(const std::string &nodeName, const std::string &modelPath); + /** + * @brief Add a node to the scene + * + * @param object + */ + virtual void addNodeToScene(const NodePath &object); protected: const std::string m_name; //! name of the renderer diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h new file mode 100644 index 0000000000..3542aafffe --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -0,0 +1,69 @@ +#ifndef vpPanda3DRGBRenderer_h +#define vpPanda3DRGBRenderer_h + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include +#include + +#include + +class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer +{ +public: + vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB") { } + + void setupScene() vp_override + { + vpPanda3DBaseRenderer::setupScene(); + m_renderRoot.set_shader_auto(); + // PT(DirectionalLight) d_light; + // d_light = new DirectionalLight("my d_light"); + // d_light->set_color(LColor(0.8, 0.8, 0.5, 1)); + // NodePath dlnp = m_renderRoot.attach_new_node(d_light); + // dlnp.set_hpr(-30, -60, 0); + // m_renderRoot.set_light(dlnp); + } + + void setupRenderTarget() vp_override; + + void getRender(vpImage &I) const; + + + void addNodeToScene(const NodePath &object) vp_override + { + NodePath objectInScene = object.copy_to(m_renderRoot); + objectInScene.set_name(object.get_name()); + objectInScene.set_shader_auto(); + PT(Material) mat = new Material(); + mat->set_diffuse(LColor(1.0, 1.0, 0.0, 1.0)); + mat->set_metallic(0.5); + mat->set_specular(0.5); + + objectInScene.set_material(mat); + m_renderRoot.ls(); + } + // { + // // NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); + // // objectInScene.set_name(object.get_name()); + // //objectInScene.set_shader_auto(); + // PT(Material) mat = new Material(); + // // mat->set_diffuse(LColor(1.0, 1.0, 0.0, 1.0)); + // // mat->set_metallic(0.5); + // // mat->set_specular(0.5); + + // // objectInScene.set_material(mat); + // } + +private: + PT(Shader) m_colorShader; + Texture *m_colorTexture; + GraphicsOutput *m_colorBuffer; + +}; + + +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h new file mode 100644 index 0000000000..a9cbe3eba8 --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h @@ -0,0 +1,99 @@ +#ifndef vpPanda3DRenderParameters_h +#define vpPanda3DRenderParameters_h + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include + + +class Camera; + +/** + * @brief Rendering parameters for a panda3D simulation + * + * includes: + * - Camera intrinsics + * - Image resolution + * - Clipping parameters + */ +class VISP_EXPORT vpPanda3DRenderParameters +{ +public: + vpPanda3DRenderParameters() : m_cam(), m_height(0), m_width(0), m_clipNear(0.001), m_clipFar(10.0) { } + vpPanda3DRenderParameters(const vpCameraParameters &cam, unsigned int h, unsigned int w, + double clipNear, double clipFar) + : m_cam(cam), m_height(h), m_width(w), m_clipNear(clipNear), m_clipFar(clipFar) + { } + + /** + * @brief Retrieve camera intrinsics. + * + * @return const vpCameraParameters& + */ + const vpCameraParameters &getCameraIntrinsics() const { return m_cam; } + /** + * @brief set camera intrinsics. Only camera intrinsics for a lens without distortion are supported. + * \throws if camera intrinsics have a distortion model. + */ + void setCameraIntrinsics(const vpCameraParameters &cam) + { + if (cam.get_projModel() != vpCameraParameters::perspectiveProjWithoutDistortion) { + throw vpException(vpException::badValue, "Panda3D renderer: only lenses with no distortion are supported"); + } + m_cam = cam; + } + + double getNearClippingDistance() const { return m_clipNear; } + double getFarClippingDistance() const { return m_clipFar; } + + /** + * @brief Set the clipping distance. When a panda camera uses these render parameters, objects that are closer than "near" or further than "far" will be clipped. + * + * @param near near clipping distance + * @param far far clipping distance + */ + void setClippingDistance(double near, double far) + { + if (far < near) { + std::swap(near, far); + } + m_clipNear = near; + m_clipFar = far; + } + + unsigned int getImageWidth() const { return m_width; } + unsigned int getImageHeight() const { return m_height; } + + + /** + * @brief Set the image resolution. + * When this object is given to a vpPanda3DBaseRenderer, + * this will be the resolution of the renderer's output images. + * + * @param height vertical image resolution + * @param width horizontal image resolution + */ + void setImageResolution(unsigned int height, unsigned int width) + { + m_height = height; + m_width = width; + } + + /** + * @brief Update a Panda3D camera object to use this objects's parameters. + * + * @param camera the camera for which to update the rendering parameters + * + * \throws if getImageWidth() or getImageHeight() are equal to 0. + */ + void setupPandaCamera(Camera *camera); + +private: + vpCameraParameters m_cam; + unsigned int m_height, m_width; + double m_clipNear, m_clipFar; +}; + +#endif +#endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp new file mode 100644 index 0000000000..c25bf2e1ab --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -0,0 +1,128 @@ +#include + +#if defined(VISP_HAVE_PANDA3D) + +void vpPanda3DBaseRenderer::initFramework(bool showWindow) +{ + if (m_framework.use_count() > 0) { + throw vpException(vpException::notImplementedError, + "Panda3D renderer: Reinitializing is not supported!"); + } + m_framework = std::shared_ptr(new PandaFramework()); + m_framework->open_framework(); + WindowProperties winProps; + winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); + int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + m_window = m_framework->open_window(winProps, flags); + if (m_window == nullptr) { + throw vpException(vpException::notInitialized, + "Panda3D renderer: Could not create the requested window when performing initialization."); + } + m_window->set_background_type(WindowFramework::BackgroundType::BT_black); + setupScene(); + setupCamera(); + setupRenderTarget(); + m_window->get_display_region_3d()->set_camera(m_cameraPath); +} + + +void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr framework, PT(WindowFramework) window) +{ + m_framework = framework; + m_window = window; + setupScene(); + setupCamera(); + setupRenderTarget(); + //m_window->get_display_region_3d()->set_camera(m_cameraPath); +} + +void vpPanda3DBaseRenderer::setupScene() +{ + m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_renderRoot.set_shader_auto(); +} + +void vpPanda3DBaseRenderer::setupCamera() +{ + m_cameraPath = m_window->make_camera(); + m_camera = (Camera *)m_cameraPath.node(); + // m_camera = m_window->get_camera(0); + m_cameraPath = m_renderRoot.attach_new_node(m_camera); + m_renderParameters.setupPandaCamera(m_camera); +} + +void vpPanda3DBaseRenderer::renderFrame() +{ + m_framework->get_graphics_engine()->render_frame(); + m_framework->get_graphics_engine()->sync_frame(); +} + +void vpPanda3DBaseRenderer::setCameraPose(const vpHomogeneousMatrix &wTc) +{ + if (m_camera.is_null() || m_cameraPath.is_empty()) { + throw vpException(vpException::notInitialized, "Camera was not initialized before trying to set its pose"); + } + setNodePose(m_cameraPath, wTc); +} + +vpHomogeneousMatrix vpPanda3DBaseRenderer::getCameraPose() +{ + if (m_camera.is_null()) { + throw vpException(vpException::notInitialized, "Camera was not initialized before trying to get its pose"); + } + return getNodePose(m_cameraPath); +} + + +void vpPanda3DBaseRenderer::setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) +{ + NodePath object = m_renderRoot.find(name); + setNodePose(object, wTo); +} + +void vpPanda3DBaseRenderer::setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) +{ + vpTranslationVector t = wTo.getTranslationVector(); + vpQuaternionVector q(wTo.getRotationMatrix()); + object.set_pos(t[0], t[1], t[2]); + object.set_quat(LQuaternion(q.w(), q.x(), q.y(), q.z())); +} + +vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(const std::string &name) +{ + NodePath object = m_renderRoot.find(name); + if (object.is_empty()) { + throw vpException(vpException::badValue, "Node %s was not found", name); + } + return getNodePose(object); +} + +vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(NodePath &object) +{ + const LPoint3 pos = object.get_pos(); + const LQuaternion quat = object.get_quat(); + const vpTranslationVector t(pos[0], pos[1], pos[2]); + const vpQuaternionVector q(quat.get_i(), quat.get_j(), quat.get_k(), quat.get_r()); + return vpHomogeneousMatrix(t, q); +} + + + + +NodePath vpPanda3DBaseRenderer::loadObject(const std::string &nodeName, const std::string &modelPath) +{ + NodePath model = m_window->load_model(m_framework->get_models(), modelPath); + std::cout << "After loading model" << std::endl; + model.detach_node(); + model.set_name(nodeName); + return model; +} + +void vpPanda3DBaseRenderer::addNodeToScene(const NodePath &object) +{ + NodePath objectInScene = object.copy_to(m_renderRoot); + objectInScene.set_name(object.get_name()); + m_renderRoot.ls(); +} + +#endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp new file mode 100644 index 0000000000..8ecefefbeb --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -0,0 +1,43 @@ +#include + +#if defined(VISP_HAVE_PANDA3D) + + +void vpPanda3DRGBRenderer::getRender(vpImage &I) const +{ + I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); + unsigned char *data = (unsigned char *)(&(m_colorTexture->get_ram_image().front())); + memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); +} + +void vpPanda3DRGBRenderer::setupRenderTarget() +{ + FrameBufferProperties fbp; + fbp.set_rgb_color(true); + fbp.set_float_depth(false); + fbp.set_float_color(false); + fbp.set_depth_bits(16); + fbp.set_rgba_bits(8, 8, 8, 8); + + WindowProperties win_prop; + win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); + + // Don't open a window - force it to be an offscreen buffer. + int flags = GraphicsPipe::BF_refuse_window; + + GraphicsEngine *engine = m_window->get_graphics_output()->get_engine(); + GraphicsPipe *pipe = m_window->get_graphics_output()->get_pipe(); + m_colorBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, + m_window->get_graphics_output()->get_gsg(), + m_window->get_graphics_output()); + m_colorTexture = new Texture(); + fbp.setup_color_texture(m_colorTexture); + m_colorBuffer->add_render_texture(m_colorTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); + m_colorBuffer->set_clear_color(LColor(0.f)); + m_colorBuffer->set_clear_color_active(true); + DisplayRegion *region = m_colorBuffer->make_display_region(); + region->set_camera(m_cameraPath); + region->set_clear_color(LColor(0.f)); +} + +#endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp new file mode 100644 index 0000000000..f2a6467633 --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp @@ -0,0 +1,38 @@ + +#include +#if defined(VISP_HAVE_PANDA3D) + + +#include +#include + +void vpPanda3DRenderParameters::setupPandaCamera(Camera *camera) +{ + // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), + // which was itself inspired by https://discourse.panda3d.org/t/lens-camera-for-opencv-style-camera-parameterisation/15413 + // And http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl + + if (m_width == 0 || m_height == 0) { + throw vpException(vpException::dimensionError, "Cannot create a projection matrix when the image width or height is 0"); + } + + PT(MatrixLens) lens = new MatrixLens(); + const double A = (m_clipFar + m_clipNear) / (m_clipFar - m_clipNear); + const double B = -2.0 * (m_clipFar * m_clipNear) / (m_clipFar - m_clipNear); + + const double cx = m_cam.get_u0(); + const double cy = m_height - m_cam.get_v0(); + + lens->set_near_far(m_clipNear, m_clipFar); + lens->set_user_mat(LMatrix4( + m_cam.get_px(), 0, 0, 0, + 0, 0, A, 1, + 0, m_cam.get_py(), 0, 0, + 0, 0, B, 0 + )); + lens->set_film_size(m_width, m_height); + lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); + camera->set_lens(lens); + +} +#endif diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index b179a7c52d..36aa291292 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -4,13 +4,16 @@ #include #include -#include -#include + +#include + +#include #include +#include + #include "load_prc_file.h" #include - -#include +#include void convertToDisplay(const vpImage &normalsImage, const vpImage &depthImage, vpImage &normalDisplayImage, vpImage &depthDisplayImage, float minDepth, float maxDepth) @@ -28,38 +31,57 @@ void convertToDisplay(const vpImage &normalsImage, const vpImage int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.1, 0.5); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); load_prc_file_data("", "sync-video false"); load_prc_file_data("", "assert-abort 1"); std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer("geom")); + std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); renderer.addSubRenderer(geometryRenderer); + renderer.addSubRenderer(rgbRenderer); + std::cout << "Initializing framework" << std::endl; renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_object.obj"); + NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/dragon.obj"); + + // PT(Material) mat = new Material(); + // mat->set_diffuse(LColor(0.0, 1.0, 0.0, 1.0)); + // mat->set_metallic(0.5); + // mat->set_specular(0.5); + + // object.set_material(mat); + // object.set_shader_auto(); + std::cout << "Adding node to scene" <set_color(LColor(0.2, 0.2, 0.2, 1.0)); - // NodePath alnp = base->getRenderRoot().attach_new_node(alight); - // base->getRenderRoot().set_light(alnp); + PT(DirectionalLight) alight = new DirectionalLight("alight"); + alight->set_color(LColor(1.0, 0.0, 0.0, 1.0)); + alight->set_direction(LVector3(0.f, 1.f, 0.f)); + NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); + rgbRenderer->getRenderRoot().set_light(alnp); + rgbRenderer->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); + alnp.set_pos(0, -5, 0); + + std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5.0, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -0.2, 0.0, 0.0, 0.0, 0.0)); vpImage normalsImage; vpImage depthImage; + vpImage colorImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpImage normalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpImage depthDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpDisplayX dNormals(normalDisplayImage, 0, 0, "normals"); vpDisplayX dDepth(depthDisplayImage, renderParams.getImageWidth(), 0, "depth"); + vpDisplayX dColor(colorImage, renderParams.getImageWidth() * 2, 0, "color"); @@ -67,8 +89,10 @@ int main() const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); renderer.getRenderer()->getRender(normalsImage, depthImage); + renderer.getRenderer()->getRender(colorImage); + - convertToDisplay(normalsImage, depthImage, normalDisplayImage, depthDisplayImage, 4, 8.0); + convertToDisplay(normalsImage, depthImage, normalDisplayImage, depthDisplayImage, 0.0, 1.0); const double afterRender = vpTime::measureTimeMs(); const double delta = (afterRender - beforeRender) / 1000.0; @@ -77,6 +101,8 @@ int main() vpDisplay::display(depthDisplayImage); vpDisplay::flush(depthDisplayImage); + vpDisplay::display(colorImage); + vpDisplay::flush(colorImage); std::cout << "Before get pose" << std::endl; From 93280c1822ab53c732680fdd33aa07034aaec711 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Thu, 18 Apr 2024 18:10:47 +0200 Subject: [PATCH 10/65] Debugging RGB renderer --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 23 +++++++++++-------- .../include/visp3/ar/vpPanda3DRendererSet.h | 1 + .../vpPanda3DRGBRenderer.cpp | 2 +- tutorial/ar/tutorial-panda3d-renderer.cpp | 23 ++++++++----------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 3542aafffe..24f2bb8600 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -18,7 +18,7 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer void setupScene() vp_override { vpPanda3DBaseRenderer::setupScene(); - m_renderRoot.set_shader_auto(); + // m_renderRoot.set_shader_auto(-100); // PT(DirectionalLight) d_light; // d_light = new DirectionalLight("my d_light"); // d_light->set_color(LColor(0.8, 0.8, 0.5, 1)); @@ -34,15 +34,18 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer void addNodeToScene(const NodePath &object) vp_override { - NodePath objectInScene = object.copy_to(m_renderRoot); - objectInScene.set_name(object.get_name()); - objectInScene.set_shader_auto(); - PT(Material) mat = new Material(); - mat->set_diffuse(LColor(1.0, 1.0, 0.0, 1.0)); - mat->set_metallic(0.5); - mat->set_specular(0.5); - - objectInScene.set_material(mat); + NodePath object2 = loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/dragon.obj"); + NodePath objectInScene = object2.copy_to(m_renderRoot); + objectInScene.set_name(object2.get_name()); + objectInScene.set_color(LColor(1.0, 0.0, 0.0, 0.0)); + objectInScene.hide(); + // PT(Material) mat = new Material(); + // mat->set_diffuse(LColor(1.0, 0.0, 0.0, 1.0)); + // mat->set_metallic(1.0); + // mat->set_specular(0.5); + // objectInScene.set_material(mat); + // objectInScene.set_shader_auto(); + std::cout << "before rgb render ls" << std::endl; m_renderRoot.ls(); } // { diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index f5acd594e2..c810749752 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -68,6 +68,7 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer { throw vpException(vpException::badValue, "NodePath setNodePose is not supported in renderer set, prefer the string version"); } + vpHomogeneousMatrix getNodePose(const std::string &name) vp_override { if (m_subRenderers.size() == 0) { diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 8ecefefbeb..9538472130 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -27,7 +27,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() GraphicsEngine *engine = m_window->get_graphics_output()->get_engine(); GraphicsPipe *pipe = m_window->get_graphics_output()->get_pipe(); - m_colorBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, + m_colorBuffer = engine->make_output(pipe, "Color Buffer", -100, fbp, win_prop, flags, m_window->get_graphics_output()->get_gsg(), m_window->get_graphics_output()); m_colorTexture = new Texture(); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 36aa291292..a03c1e8fe2 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -61,13 +61,13 @@ int main() renderer.addNodeToScene(object); - PT(DirectionalLight) alight = new DirectionalLight("alight"); - alight->set_color(LColor(1.0, 0.0, 0.0, 1.0)); - alight->set_direction(LVector3(0.f, 1.f, 0.f)); - NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); - rgbRenderer->getRenderRoot().set_light(alnp); - rgbRenderer->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); - alnp.set_pos(0, -5, 0); + // PT(DirectionalLight) alight = new DirectionalLight("alight"); + // alight->set_color(LColor(1.0, 0.0, 0.0, 1.0)); + // alight->set_direction(LVector3(0.f, 1.f, 0.f)); + // NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); + // rgbRenderer->getRenderRoot().set_light(alnp); + + // alnp.set_pos(0, -5, 0); std::cout << "Setting camera pose" << std::endl; @@ -105,13 +105,10 @@ int main() vpDisplay::flush(colorImage); - std::cout << "Before get pose" << std::endl; vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); - std::cout << wTo << std::endl; - vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); - renderer.setNodePose("cube", wTo * oToo); - std::cout << object.get_scale() << std::endl; - std::cout << "After set pose " << std::endl; + // vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + // renderer.setNodePose("cube", wTo * oToo); + renderer.getRenderer()->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; } return 0; From 49af00a4a9101776f792318d3f029281339ca917 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Thu, 18 Apr 2024 19:05:43 +0200 Subject: [PATCH 11/65] working rgb renderer --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 31 +++++++++---------- .../vpPanda3DBaseRenderer.cpp | 1 + tutorial/ar/tutorial-panda3d-renderer.cpp | 13 ++++---- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 24f2bb8600..6186a656b7 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -18,13 +18,14 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer void setupScene() vp_override { vpPanda3DBaseRenderer::setupScene(); - // m_renderRoot.set_shader_auto(-100); - // PT(DirectionalLight) d_light; - // d_light = new DirectionalLight("my d_light"); - // d_light->set_color(LColor(0.8, 0.8, 0.5, 1)); - // NodePath dlnp = m_renderRoot.attach_new_node(d_light); + m_renderRoot.set_shader_auto(); + PT(DirectionalLight) d_light; + d_light = new DirectionalLight("my d_light"); + d_light->set_color(LColor(1.0, 1.0, 1.0, 1)); + d_light->set_direction(LVector3(0.5, 0.5, 0.0).normalized()); + NodePath dlnp = m_renderRoot.attach_new_node(d_light); // dlnp.set_hpr(-30, -60, 0); - // m_renderRoot.set_light(dlnp); + m_renderRoot.set_light(dlnp); } void setupRenderTarget() vp_override; @@ -34,17 +35,13 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer void addNodeToScene(const NodePath &object) vp_override { - NodePath object2 = loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/dragon.obj"); - NodePath objectInScene = object2.copy_to(m_renderRoot); - objectInScene.set_name(object2.get_name()); - objectInScene.set_color(LColor(1.0, 0.0, 0.0, 0.0)); - objectInScene.hide(); - // PT(Material) mat = new Material(); - // mat->set_diffuse(LColor(1.0, 0.0, 0.0, 1.0)); - // mat->set_metallic(1.0); - // mat->set_specular(0.5); - // objectInScene.set_material(mat); - // objectInScene.set_shader_auto(); + NodePath objectInScene = object.copy_to(m_renderRoot); + objectInScene.set_name(object.get_name()); + PT(Material) mat = new Material(); + mat->set_base_color(LColor(1.0, 0.0, 0.0, 1.0)); + mat->set_metallic(1.0); + mat->set_specular(1.0); + objectInScene.set_material(mat); std::cout << "before rgb render ls" << std::endl; m_renderRoot.ls(); } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index c25bf2e1ab..35f47115b2 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -49,6 +49,7 @@ void vpPanda3DBaseRenderer::setupCamera() // m_camera = m_window->get_camera(0); m_cameraPath = m_renderRoot.attach_new_node(m_camera); m_renderParameters.setupPandaCamera(m_camera); + m_camera->set_scene(m_renderRoot); } void vpPanda3DBaseRenderer::renderFrame() diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index a03c1e8fe2..52f748b419 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -31,7 +31,7 @@ void convertToDisplay(const vpImage &normalsImage, const vpImage int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.1, 0.5); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.1, 5); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -47,7 +47,7 @@ int main() renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/dragon.obj"); + NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); // PT(Material) mat = new Material(); // mat->set_diffuse(LColor(0.0, 1.0, 0.0, 1.0)); @@ -71,7 +71,7 @@ int main() std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -0.2, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5, 0.0, 0.0, 0.0, 0.0)); vpImage normalsImage; vpImage depthImage; @@ -106,9 +106,10 @@ int main() vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); - // vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); - // renderer.setNodePose("cube", wTo * oToo); - renderer.getRenderer()->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); + vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + renderer.setNodePose("cube", wTo * oToo); + + // renderer.getRenderer()->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; } return 0; From 968d252e3da4e56812c84418e8021658e931e842 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 19 Apr 2024 12:23:55 +0200 Subject: [PATCH 12/65] Testing Camera space normals --- .../visp3/ar/vpPanda3DGeometryRenderer.h | 3 ++- .../vpPanda3DGeometryRenderer.cpp | 18 ++++++++++++++--- tutorial/ar/tutorial-panda3d-renderer.cpp | 20 +++++++++++-------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 9df86661bd..ea255deb5b 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -25,7 +25,8 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer void getRender(vpImage &normals, vpImage &depth) const; - static const char *SHADER_VERT_NORMAL_AND_DEPTH; + static const char *SHADER_VERT_NORMAL_AND_DEPTH_WORLD; + static const char *SHADER_VERT_NORMAL_AND_DEPTH_CAMERA; static const char *SHADER_FRAG_NORMAL_AND_DEPTH; private: diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 80fb8d80c7..9c6e51fdfb 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -5,7 +5,19 @@ #include -const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH = R"shader( +const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( +#version 140 +in vec3 p3d_Normal; +varying vec3 oNormal; +uniform mat3 p3d_NormalMatrix; +void main() +{ + gl_Position = ftransform(); + oNormal.zyx = p3d_NormalMatrix * normalize(p3d_Normal); +} +)shader"; + +const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_WORLD = R"shader( #version 120 varying vec3 oNormal; @@ -38,7 +50,7 @@ void vpPanda3DGeometryRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); m_normalDepthShader = Shader::make(Shader::ShaderLanguage::SL_GLSL, - SHADER_VERT_NORMAL_AND_DEPTH, + SHADER_VERT_NORMAL_AND_DEPTH_CAMERA, SHADER_FRAG_NORMAL_AND_DEPTH); m_renderRoot.set_shader(m_normalDepthShader); @@ -82,7 +94,7 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_y_size(), m_normalDepthTexture->get_x_size()); float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); -#pragma omp parallel for simd +//#pragma omp parallel for simd for (unsigned int i = 0; i < normals.getRows() * normals.getCols(); ++i) { normals.bitmap[i].R = (data[i * 4]); normals.bitmap[i].G = (data[i * 4 + 1]); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 52f748b419..c229a21786 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -24,14 +24,14 @@ void convertToDisplay(const vpImage &normalsImage, const vpImage normalDisplayImage.bitmap[i].R = static_cast((normalsImage.bitmap[i].R + 1.0) * 127.5f); normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); normalDisplayImage.bitmap[i].B = static_cast((normalsImage.bitmap[i].B + 1.0) * 127.5f); - depthDisplayImage.bitmap[i] = static_cast(((depthImage.bitmap[i] - minDepth) / (maxDepth - minDepth)) * 255.f); + depthDisplayImage.bitmap[i] = static_cast(depthImage.bitmap[i] * 255.f); } } int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.1, 5); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.3, 1.5); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -47,7 +47,7 @@ int main() renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject("cube", "/home/sfelton/software/visp-sfelton/tutorial/ar/data/simple_cube.obj"); + NodePath object = renderer.loadObject("cube", "/home/sfelton/buddha.obj"); // PT(Material) mat = new Material(); // mat->set_diffuse(LColor(0.0, 1.0, 0.0, 1.0)); @@ -71,7 +71,7 @@ int main() std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -5, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -1, 0.0, 0.0, 0.0, 0.0)); vpImage normalsImage; vpImage depthImage; @@ -88,14 +88,13 @@ int main() while (true) { const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); + const double beforeFetch = vpTime::measureTimeMs(); renderer.getRenderer()->getRender(normalsImage, depthImage); renderer.getRenderer()->getRender(colorImage); - + const double beforeConvert = vpTime::measureTimeMs(); convertToDisplay(normalsImage, depthImage, normalDisplayImage, depthDisplayImage, 0.0, 1.0); - const double afterRender = vpTime::measureTimeMs(); - const double delta = (afterRender - beforeRender) / 1000.0; vpDisplay::display(normalDisplayImage); vpDisplay::flush(normalDisplayImage); @@ -105,12 +104,17 @@ int main() vpDisplay::flush(colorImage); + const double afterAll = vpTime::measureTimeMs(); + const double delta = (afterAll - beforeRender) / 1000.0; vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); renderer.setNodePose("cube", wTo * oToo); // renderer.getRenderer()->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); - std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << afterRender - beforeRender << "ms" << std::endl; + std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << beforeFetch - beforeRender << "ms" << std::endl; + std::cout << "Copying to vpImage took: " << std::fixed << std::setprecision(2) << beforeConvert - beforeFetch << "ms" << std::endl; + std::cout << "display took: " << std::fixed << std::setprecision(2) << afterAll - beforeConvert << "ms" << std::endl; + } return 0; } From f21fd2f9c729edb2a1245e424be5bf60a80a30c1 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 19 Apr 2024 16:38:45 +0200 Subject: [PATCH 13/65] Display all potential renders in tutorial, improve assert guards, many fixes and cleanup included --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 82 +++++++-- .../visp3/ar/vpPanda3DGeometryRenderer.h | 37 ++-- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 48 +---- .../vpPanda3DBaseRenderer.cpp | 37 +++- .../vpPanda3DGeometryRenderer.cpp | 35 +++- .../vpPanda3DRGBRenderer.cpp | 1 + tutorial/ar/data/simple_object.mtl | 10 -- tutorial/ar/data/simple_object.obj | 164 ------------------ tutorial/ar/tutorial-panda3d-renderer.cpp | 129 +++++++++----- 9 files changed, 250 insertions(+), 293 deletions(-) delete mode 100644 tutorial/ar/data/simple_object.mtl delete mode 100644 tutorial/ar/data/simple_object.obj diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 8316125d5f..a0fc62572b 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -24,7 +24,11 @@ class VISP_EXPORT vpPanda3DBaseRenderer { public: - vpPanda3DBaseRenderer(const std::string &rendererName) : m_name(rendererName), m_framework(nullptr), m_window(nullptr) { } + vpPanda3DBaseRenderer(const std::string &rendererName) + : m_name(rendererName), m_framework(nullptr), m_window(nullptr), m_camera(nullptr) + { + setVerticalSyncEnabled(false); + } virtual ~vpPanda3DBaseRenderer() = default; @@ -44,21 +48,6 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ void initFromParent(std::shared_ptr framework, PT(WindowFramework) window); - /** - * @brief Initialize the scene for this specific renderer. - * - * Creates a root scene for this node and applies shaders. that will be used for rendering - * - */ - virtual void setupScene(); - - /** - * @brief Initialize camera. Should be called when the scene root of this render has already been created. - * - */ - virtual void setupCamera(); - - virtual void setupRenderTarget() { } virtual void renderFrame(); @@ -68,6 +57,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @return const std::string& */ const std::string &getName() const { return m_name; } + /** * @brief Get the scene root * @@ -98,6 +88,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @brief Retrieve the camera's pose, in the world frame. */ virtual vpHomogeneousMatrix getCameraPose(); + /** * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). * @@ -115,6 +106,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @param wTo Pose of the object in the world frame */ virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo); + /** * @brief Get the pose of a Panda node, in world frame. * @@ -123,11 +115,29 @@ class VISP_EXPORT vpPanda3DBaseRenderer * \throws if no node can be found from the given path. */ virtual vpHomogeneousMatrix getNodePose(const std::string &name); + /** * @brief Get the pose of a Panda node, in world frame. This version of the method directly uses the Panda Nodepath. */ virtual vpHomogeneousMatrix getNodePose(NodePath &object); + /** + * @brief Compute the near and far planes for the camera at the current pose, given a certain node/part of the graph. + * + * The near clipping value will be set to the distance to the closest point of the object. + * The far clipping value will be set to the distance to farthest vertex of the object. + * + * \warning Depending on geometry complexity, this may be an expensive operation. + * \warning if the object lies partly behind the camera, the near plane value will be zero. + * If it fully behind, the far plane will also be zero. If these near/far values are used to update the + * rendering parameters of the camera, this may result in an invalid projection matrix. + * + * @param name name of the node that should be used to compute near and far values. + * @param near resulting near clipping plane distance + * @param far resulting far clipping plane distance + */ + void computeNearAndFarPlanesFromNode(const std::string &name, float &near, float &far); + /** * @brief Load a 3D object. To load an .obj file, Panda3D must be compiled with assimp support. * @@ -140,12 +150,50 @@ class VISP_EXPORT vpPanda3DBaseRenderer NodePath loadObject(const std::string &nodeName, const std::string &modelPath); /** - * @brief Add a node to the scene + * @brief Add a node to the scene. * * @param object */ virtual void addNodeToScene(const NodePath &object); + /** + * @brief set whether vertical sync is enabled. + * When vertical sync is enabled, render speed will be limited by the display's refresh rate + * + * @param useVsync Whether to use vsync or not + */ + void setVerticalSyncEnabled(bool useVsync); + /** + * @brief Set the behaviour when a Panda3D assertion fails. If abort is true, the program will stop. + * Otherwise, an error will be displayed in the console. + * + * @param abort whether to abort (true) or display a message when an assertion fails. + */ + void setAbortOnPandaError(bool abort); + + +protected: + + /** + * @brief Initialize the scene for this specific renderer. + * + * Creates a root scene for this node and applies shaders. that will be used for rendering + * + */ + virtual void setupScene(); + + /** + * @brief Initialize camera. Should be called when the scene root of this render has already been created. + * + */ + virtual void setupCamera(); + + /** + * @brief Initialize buffers and other objects that are required to save the render. + * + */ + virtual void setupRenderTarget() { } + protected: const std::string m_name; //! name of the renderer std::shared_ptr m_framework; //! Pointer to the active panda framework diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index ea255deb5b..a3706b565c 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -9,31 +9,48 @@ #include /** - * @brief Renderer that outputs object normals (in world frame) as well as depth + * @brief Renderer that outputs object geometric information. * + * This information may contain, depending on requested render type: + * + * - Normals in the world frame or in the camera frame + * - Depth information */ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer { public: - vpPanda3DGeometryRenderer(const std::string &rendererName) : vpPanda3DBaseRenderer(rendererName) { } + + enum vpRenderType + { + WORLD_NORMALS, //! Surface normals in world space. + CAMERA_NORMALS, //! Surface normals in the frame of the camera + }; + + vpPanda3DGeometryRenderer(vpRenderType renderType); ~vpPanda3DGeometryRenderer() { } + /** + * @brief Get render results into ViSP readable structures + * + * + * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + * @param depth Image used to store depth + */ + void getRender(vpImage &colorData, vpImage &depth) const; + +protected: void setupScene() vp_override; void setupRenderTarget() vp_override; - - void getRender(vpImage &normals, vpImage &depth) const; - +private: + vpRenderType m_renderType; + Texture *m_normalDepthTexture; + GraphicsOutput *m_normalDepthBuffer; static const char *SHADER_VERT_NORMAL_AND_DEPTH_WORLD; static const char *SHADER_VERT_NORMAL_AND_DEPTH_CAMERA; static const char *SHADER_FRAG_NORMAL_AND_DEPTH; -private: - PT(Shader) m_normalDepthShader; - Texture *m_normalDepthTexture; - GraphicsOutput *m_normalDepthBuffer; - }; diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 6186a656b7..87589afbe8 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -15,50 +15,20 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer public: vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB") { } - void setupScene() vp_override - { - vpPanda3DBaseRenderer::setupScene(); - m_renderRoot.set_shader_auto(); - PT(DirectionalLight) d_light; - d_light = new DirectionalLight("my d_light"); - d_light->set_color(LColor(1.0, 1.0, 1.0, 1)); - d_light->set_direction(LVector3(0.5, 0.5, 0.0).normalized()); - NodePath dlnp = m_renderRoot.attach_new_node(d_light); - // dlnp.set_hpr(-30, -60, 0); - m_renderRoot.set_light(dlnp); - } - - void setupRenderTarget() vp_override; - + /** + * @brief Store the render resulting from calling renderFrame() into a vpImage. + * + * If the image does not have the correct dimensions, it is resized. + * + * @param I The image in which to store the render. + */ void getRender(vpImage &I) const; +protected: - void addNodeToScene(const NodePath &object) vp_override - { - NodePath objectInScene = object.copy_to(m_renderRoot); - objectInScene.set_name(object.get_name()); - PT(Material) mat = new Material(); - mat->set_base_color(LColor(1.0, 0.0, 0.0, 1.0)); - mat->set_metallic(1.0); - mat->set_specular(1.0); - objectInScene.set_material(mat); - std::cout << "before rgb render ls" << std::endl; - m_renderRoot.ls(); - } - // { - // // NodePath objectInScene = m_renderRoot.attach_new_node(object.node()); - // // objectInScene.set_name(object.get_name()); - // //objectInScene.set_shader_auto(); - // PT(Material) mat = new Material(); - // // mat->set_diffuse(LColor(1.0, 1.0, 0.0, 1.0)); - // // mat->set_metallic(0.5); - // // mat->set_specular(0.5); - - // // objectInScene.set_material(mat); - // } + void setupRenderTarget() vp_override; private: - PT(Shader) m_colorShader; Texture *m_colorTexture; GraphicsOutput *m_colorBuffer; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 35f47115b2..b75b55eeff 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -2,6 +2,8 @@ #if defined(VISP_HAVE_PANDA3D) +#include "load_prc_file.h" + void vpPanda3DBaseRenderer::initFramework(bool showWindow) { if (m_framework.use_count() > 0) { @@ -107,7 +109,20 @@ vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(NodePath &object) return vpHomogeneousMatrix(t, q); } - +void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &name, float &near, float &far) +{ + if (m_camera == nullptr) { + throw vpException(vpException::notInitialized, "Cannot compute planes when the camera is not initialized"); + } + NodePath object = m_renderRoot.find(name); + if (object.is_empty()) { + throw vpException(vpException::badValue, "Node %s was not found", name); + } + LPoint3 minP, maxP; + object.calc_tight_bounds(minP, maxP, m_cameraPath); + near = std::max(0.f, minP.get_y()); + far = std::max(near, maxP.get_y()); +} NodePath vpPanda3DBaseRenderer::loadObject(const std::string &nodeName, const std::string &modelPath) @@ -123,7 +138,25 @@ void vpPanda3DBaseRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); - m_renderRoot.ls(); +} + +void vpPanda3DBaseRenderer::setVerticalSyncEnabled(bool useVsync) +{ + if (useVsync) { + load_prc_file_data("", "sync-video true"); + } + else { + load_prc_file_data("", "sync-video false"); + } +} +void vpPanda3DBaseRenderer::setAbortOnPandaError(bool abort) +{ + if (abort) { + load_prc_file_data("", "assert-abort 1"); + } + else { + load_prc_file_data("", "assert-abort 0"); + } } #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 9c6e51fdfb..903b43fa68 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -13,7 +13,9 @@ uniform mat3 p3d_NormalMatrix; void main() { gl_Position = ftransform(); - oNormal.zyx = p3d_NormalMatrix * normalize(p3d_Normal); + // View space is Z-up right handed, flip z and y + oNormal = p3d_NormalMatrix * normalize(p3d_Normal); + //oNormal.yz = -oNormal.zy; } )shader"; @@ -45,16 +47,35 @@ void main() } )shader"; +std::string renderTypeToName(vpPanda3DGeometryRenderer::vpRenderType type) +{ + switch (type) { + case vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS: + return "normals-world"; + case vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS: + return "normals-camera"; + default: + return ""; + } +} + +vpPanda3DGeometryRenderer::vpPanda3DGeometryRenderer(vpRenderType renderType) : vpPanda3DBaseRenderer(renderTypeToName(renderType)), m_renderType(renderType) { } void vpPanda3DGeometryRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_normalDepthShader = Shader::make(Shader::ShaderLanguage::SL_GLSL, - SHADER_VERT_NORMAL_AND_DEPTH_CAMERA, - SHADER_FRAG_NORMAL_AND_DEPTH); - m_renderRoot.set_shader(m_normalDepthShader); - - + PT(Shader) shader; + if (m_renderType == WORLD_NORMALS) { + shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + SHADER_VERT_NORMAL_AND_DEPTH_WORLD, + SHADER_FRAG_NORMAL_AND_DEPTH); + } + else if (m_renderType == CAMERA_NORMALS) { + shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + SHADER_VERT_NORMAL_AND_DEPTH_CAMERA, + SHADER_FRAG_NORMAL_AND_DEPTH); + } + m_renderRoot.set_shader(shader); } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 9538472130..4de6fd76fe 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -10,6 +10,7 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); } + void vpPanda3DRGBRenderer::setupRenderTarget() { FrameBufferProperties fbp; diff --git a/tutorial/ar/data/simple_object.mtl b/tutorial/ar/data/simple_object.mtl deleted file mode 100644 index 34c0586781..0000000000 --- a/tutorial/ar/data/simple_object.mtl +++ /dev/null @@ -1,10 +0,0 @@ -# Blender 3.4.1 MTL File: 'None' -# www.blender.org - -newmtl Material -Kd 0.800000 0.000000 0.001367 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.450000 -d 1.000000 -illum 3 diff --git a/tutorial/ar/data/simple_object.obj b/tutorial/ar/data/simple_object.obj deleted file mode 100644 index 5d75c7b2bb..0000000000 --- a/tutorial/ar/data/simple_object.obj +++ /dev/null @@ -1,164 +0,0 @@ -# Blender 3.4.1 -# www.blender.org -mtllib simple_object.mtl -o Cube -v 0.473170 0.738021 -0.738021 -v 0.473170 -0.738021 -0.738021 -v 0.473170 0.738021 0.738021 -v 0.473170 -0.738021 0.738021 -v -0.473170 0.738021 -0.738021 -v -0.473170 -0.738021 -0.738021 -v -0.473170 0.738021 0.738021 -v -0.473170 -0.738021 0.738021 -v -0.473170 -0.738021 0.427758 -v 0.198234 0.525310 0.188069 -v -0.198234 0.650917 0.188069 -v 0.473170 -0.738021 0.427758 -v 0.473170 1.111521 0.738021 -v -0.473170 1.111521 0.738021 -v 0.473170 1.111521 0.427758 -v -0.473170 1.111521 0.427758 -v -0.198234 0.650917 -0.157573 -v 0.473170 -0.738021 -0.397262 -v -0.473170 -0.738021 -0.397262 -v 0.198234 0.525310 -0.157573 -v -0.473170 1.248340 -0.397262 -v -0.473170 1.248340 -0.738021 -v 0.473170 1.248340 -0.738021 -v 0.473170 1.248340 -0.397262 -v 0.769975 0.438207 -0.397262 -v 0.769975 0.438207 0.427758 -v 0.769975 -0.738021 0.427758 -v 0.769975 -0.738021 -0.397262 -v 0.319294 -0.028908 -0.377806 -v -0.170810 -0.028908 0.386627 -v 0.319294 -0.028908 0.386627 -v -0.170810 -0.028908 -0.377806 -v 0.248100 -0.083989 0.163877 -v -0.099616 -0.051464 0.163877 -v 0.248100 -0.083989 -0.139262 -v -0.099616 -0.051464 -0.139262 -v 0.473008 -0.106544 0.225944 -v 0.473008 -0.106544 -0.201329 -vn -0.9338 -0.2287 -0.2753 -vn -0.0000 -0.4165 0.9091 -vn -0.9172 -0.1558 0.3668 -vn -0.0000 -1.0000 -0.0000 -vn 0.9055 0.0467 -0.4218 -vn -0.0000 -0.4251 -0.9051 -vn -0.3640 -0.0941 -0.9267 -vn -0.9101 -0.1496 -0.3864 -vn 0.3020 0.9533 -0.0000 -vn -0.0000 1.0000 -0.0000 -vn 0.0389 0.4160 -0.9085 -vn -0.0000 -0.0000 1.0000 -vn 0.9454 -0.2082 -0.2507 -vn -0.0000 -0.0000 -1.0000 -vn 0.9155 0.0409 0.4003 -vn -0.9903 -0.1390 -0.0000 -vn 0.0319 0.3411 0.9395 -vn -0.9475 -0.1776 0.2660 -vn 0.9549 -0.1649 0.2469 -vn 0.8780 -0.4786 -0.0000 -vn 0.1506 0.9886 -0.0000 -vn -0.0997 0.3029 0.9478 -vn -0.3647 -0.1031 0.9254 -vn 0.9049 0.4256 -0.0000 -vn -0.8784 0.4779 -0.0000 -vn 0.9686 0.2064 0.1387 -vn -0.9139 0.3817 -0.1382 -vn -0.1003 0.2948 -0.9503 -vn -0.0000 0.4529 -0.8916 -vn 0.9680 0.2013 -0.1501 -vn -0.9136 0.3861 0.1273 -vn -0.0000 0.4440 0.8960 -vt 0.625000 0.500000 -vt 0.375000 0.500000 -vt 0.625000 0.750000 -vt 0.375000 0.750000 -vt 0.625000 0.250000 -vt 0.125000 0.500000 -vt 0.375000 0.250000 -vt 0.625000 0.000000 -vt 0.625000 1.000000 -vt 0.125000 0.750000 -vt 0.375000 0.000000 -vt 0.375000 1.000000 -vt 0.125000 0.697450 -vt 0.375000 0.052550 -vt 0.670572 0.677152 -vt 0.802369 0.656853 -vt 0.615165 0.093146 -vt 0.375000 0.697450 -vt 0.625000 0.750000 -vt 0.625000 0.000000 -vt 0.875000 0.750000 -vt 0.625000 1.000000 -vt 0.625000 0.697450 -vt 0.625000 0.052550 -vt 0.875000 0.697450 -vt 0.802369 0.598312 -vt 0.615165 0.151688 -vt 0.375000 0.557715 -vt 0.125000 0.557715 -vt 0.375000 0.192285 -vt 0.670572 0.578013 -vt 0.875000 0.557715 -vt 0.625000 0.192285 -vt 0.875000 0.500000 -vt 0.625000 0.250000 -vt 0.625000 0.500000 -vt 0.625000 0.557715 -vt 0.625000 0.557715 -vt 0.625000 0.697450 -vt 0.375000 0.697450 -vt 0.375000 0.557715 -vt 0.532627 0.557034 -vt 0.501436 0.083267 -vt 0.495104 0.920124 -vt 0.533904 0.695375 -vt 0.501624 0.165036 -vt 0.533436 0.683847 -vt 0.511781 0.097244 -vt 0.533329 0.571291 -vt 0.511781 0.148587 -vt 0.520549 0.690019 -vt 0.520451 0.565125 -s 0 -usemtl Material -f 11/17/1 7/8/1 14/20/1 16/24/1 -f 31/45/2 3/3/2 7/9/2 30/44/2 -f 36/50/3 17/27/3 5/5/3 32/46/3 -f 9/13/4 12/18/4 4/4/4 8/10/4 -f 33/47/5 10/15/5 3/3/5 31/45/5 -f 32/46/6 5/5/6 1/1/6 29/42/6 -f 35/49/7 20/31/7 25/38/7 38/52/7 -f 19/29/4 18/28/4 12/18/4 9/13/4 -f 30/43/8 7/8/8 11/17/8 34/48/8 -f 20/31/9 17/26/9 11/16/9 10/15/9 -f 15/23/10 16/25/10 14/21/10 13/19/10 -f 10/15/11 11/16/11 16/25/11 15/23/11 -f 7/9/12 3/3/12 13/19/12 14/22/12 -f 3/3/13 10/15/13 15/23/13 13/19/13 -f 1/1/14 5/5/14 22/35/14 23/36/14 -f 6/6/4 2/2/4 18/28/4 19/29/4 -f 29/42/15 1/1/15 20/31/15 35/49/15 -f 34/48/16 11/17/16 17/27/16 36/50/16 -f 23/36/10 22/34/10 21/32/10 24/37/10 -f 17/26/17 20/31/17 24/37/17 21/32/17 -f 5/5/18 17/27/18 21/33/18 22/35/18 -f 20/31/19 1/1/19 23/36/19 24/37/19 -f 38/52/20 25/38/20 26/39/20 37/51/20 -f 20/31/21 10/15/21 26/39/21 25/38/21 -f 12/18/4 18/28/4 28/41/4 27/40/4 -f 33/47/22 12/18/22 27/40/22 37/51/22 -f 10/15/23 33/47/23 37/51/23 26/39/23 -f 28/41/24 38/52/24 37/51/24 27/40/24 -f 9/14/25 34/48/25 36/50/25 19/30/25 -f 2/2/26 29/42/26 35/49/26 18/28/26 -f 8/11/27 30/43/27 34/48/27 9/14/27 -f 18/28/28 35/49/28 38/52/28 28/41/28 -f 6/7/29 32/46/29 29/42/29 2/2/29 -f 12/18/30 33/47/30 31/45/30 4/4/30 -f 19/30/31 36/50/31 32/46/31 6/7/31 -f 4/4/32 31/45/32 30/44/32 8/12/32 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index c229a21786..7fa456c4dc 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,116 +1,158 @@ #include #include -#if defined(VISP_HAVE_PANDA3D) +#if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) #include #include #include +#include +#include +#include +#include + + #include #include #include -#include "load_prc_file.h" #include #include -void convertToDisplay(const vpImage &normalsImage, const vpImage &depthImage, - vpImage &normalDisplayImage, vpImage &depthDisplayImage, float minDepth, float maxDepth) + +void displayNormals(const vpImage &normalsImage, + vpImage &normalDisplayImage) { - std::cout << depthImage[0][0] << std::endl; #pragma omp parallel for simd for (unsigned int i = 0; i < normalsImage.getSize(); ++i) { normalDisplayImage.bitmap[i].R = static_cast((normalsImage.bitmap[i].R + 1.0) * 127.5f); normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); normalDisplayImage.bitmap[i].B = static_cast((normalsImage.bitmap[i].B + 1.0) * 127.5f); + } + vpDisplay::display(normalDisplayImage); + vpDisplay::flush(normalDisplayImage); +} +void displayDepth(const vpImage &depthImage, + vpImage &depthDisplayImage) +{ +#pragma omp parallel for simd + for (unsigned int i = 0; i < depthImage.getSize(); ++i) { depthDisplayImage.bitmap[i] = static_cast(depthImage.bitmap[i] * 255.f); } + vpDisplay::display(depthDisplayImage); + vpDisplay::flush(depthDisplayImage); } - int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.3, 1.5); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 480, 640, 0.01, 1.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); + const std::string objectName = "object"; + - load_prc_file_data("", "sync-video false"); - load_prc_file_data("", "assert-abort 1"); - std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer("geom")); + + std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS)); + + std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); renderer.addSubRenderer(geometryRenderer); + renderer.addSubRenderer(cameraRenderer); renderer.addSubRenderer(rgbRenderer); + renderer.setVerticalSyncEnabled(false); + renderer.setAbortOnPandaError(true); + std::cout << "Initializing framework" << std::endl; renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject("cube", "/home/sfelton/buddha.obj"); + NodePath object = renderer.loadObject(objectName, "/home/sfelton/cube.bam"); + std::cout << "Adding node to scene" <set_diffuse(LColor(0.0, 1.0, 0.0, 1.0)); - // mat->set_metallic(0.5); - // mat->set_specular(0.5); + PT(Material) mat = new Material(); + mat->set_specular(LColor(1.0, 1.0, 1.0, 1.0)); + mat->set_base_color(LColor(1.0)); + mat->set_shininess(5.0); + mat->set_metallic(5.0); + object.set_material(mat); - // object.set_material(mat); - // object.set_shader_auto(); - std::cout << "Adding node to scene" <set_color(LColor(1.0, 0.0, 0.0, 1.0)); - // alight->set_direction(LVector3(0.f, 1.f, 0.f)); - // NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); - // rgbRenderer->getRenderRoot().set_light(alnp); - - // alnp.set_pos(0, -5, 0); - + // PT(DirectionalLight) d_light; + // d_light = new DirectionalLight("my d_light"); + // d_light->set_color(LColor(10.0, 1.0, 1.0, 1)); + // d_light->set_direction(LVector3(0.5, 0.5, 0.0).normalized()); + // NodePath dlnp = rgbRenderer->getRenderRoot().attach_new_node(d_light); + // d_light->set_point(LPoint3(-5, -5, 0)); + // // dlnp.set_hpr(-30, -60, 0); + // rgbRenderer->getRenderRoot().set_light(dlnp); + PT(AmbientLight) a_light = new AmbientLight("my a_light"); + a_light->set_color(LColor(1.0, 5.0, 0.0, 1.0)); + NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(a_light); + rgbRenderer->getRenderRoot().set_light(alnp); std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -1, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, -0.4, 0.0, 0.0, 0.0, 0.0)); vpImage normalsImage; + vpImage cameraNormalsImage; + vpImage depthImage; vpImage colorImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpImage normalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); - vpImage depthDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); + vpImage cameraNormalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); - vpDisplayX dNormals(normalDisplayImage, 0, 0, "normals"); - vpDisplayX dDepth(depthDisplayImage, renderParams.getImageWidth(), 0, "depth"); - vpDisplayX dColor(colorImage, renderParams.getImageWidth() * 2, 0, "color"); + vpImage depthDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); +#if defined(VISP_HAVE_GTK) + using DisplayCls = vpDisplayGTK; +#elif defined(VISP_HAVE_X11) + using DisplayCls = vpDisplayX; +#elif defined(HAVE_OPENCV_HIGHGUI) + using DisplayCls = vpDisplayOpenCV; +#elif defined(VISP_HAVE_GDI) + using DisplayCls = vpDisplayGDI; +#elif defined(VISP_HAVE_D3D9) + using DisplayCls = vpDisplayD3D; +#endif + DisplayCls dNormals(normalDisplayImage, 0, 0, "normals in world space"); + DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, 0, "normals in camera space"); + DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth(), 0, "depth"); + DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2, 0, "color"); while (true) { + float near = 0, far = 0; + const double beforeComputeBB = vpTime::measureTimeMs(); + rgbRenderer->computeNearAndFarPlanesFromNode(objectName, near, far); + renderParams.setClippingDistance(near, far); + renderer.setRenderParameters(renderParams); + std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); const double beforeFetch = vpTime::measureTimeMs(); - renderer.getRenderer()->getRender(normalsImage, depthImage); + renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); + renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage, depthImage); renderer.getRenderer()->getRender(colorImage); const double beforeConvert = vpTime::measureTimeMs(); - convertToDisplay(normalsImage, depthImage, normalDisplayImage, depthDisplayImage, 0.0, 1.0); - - vpDisplay::display(normalDisplayImage); - vpDisplay::flush(normalDisplayImage); - vpDisplay::display(depthDisplayImage); - vpDisplay::flush(depthDisplayImage); + displayNormals(normalsImage, normalDisplayImage); + displayNormals(cameraNormalsImage, cameraNormalDisplayImage); + displayDepth(depthImage, depthDisplayImage); vpDisplay::display(colorImage); vpDisplay::flush(colorImage); - const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; - vpHomogeneousMatrix wTo = renderer.getNodePose("cube"); + vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); - renderer.setNodePose("cube", wTo * oToo); - - // renderer.getRenderer()->setNodePose("cube", vpHomogeneousMatrix(1, 0, 0.0, 0.0, 0.0, 0.0)); + renderer.setNodePose(objectName, wTo * oToo); std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << beforeFetch - beforeRender << "ms" << std::endl; std::cout << "Copying to vpImage took: " << std::fixed << std::setprecision(2) << beforeConvert - beforeFetch << "ms" << std::endl; std::cout << "display took: " << std::fixed << std::setprecision(2) << afterAll - beforeConvert << "ms" << std::endl; @@ -123,7 +165,6 @@ int main() int main() { - std::cerr << "Recompile ViSP with Panda3D as a third party to run this tutorial" << std::endl; return EXIT_FAILURE; } From 64b3f6eefb5da900623c2f9a0406926368ec024c Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 19 Apr 2024 18:05:12 +0200 Subject: [PATCH 14/65] fix color ordering --- .../vpPanda3DGeometryRenderer.cpp | 4 +-- .../vpPanda3DRGBRenderer.cpp | 14 +++++++- tutorial/ar/tutorial-panda3d-renderer.cpp | 36 ++++++++----------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 903b43fa68..27d8d5c4e9 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -117,9 +117,9 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); + std::cout << "Before request" << std::endl; unsigned char *data = (unsigned char *)(&(m_colorTexture->get_ram_image().front())); - memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); + std::cout << m_colorTexture->get_format() << std::endl; + // CPTA_uchar p((const unsigned char *)I.bitmap, ((const unsigned char *)I.bitmap) + I.getSize() * 4); + // m_colorTexture->set_ram_image_as(p, "rgba"); + std::cout << "After request" << std::endl; + // BGRA order in panda3d + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i].B = data[i * 4]; + I.bitmap[i].G = data[i * 4 + 1]; + I.bitmap[i].R = data[i * 4 + 2]; + I.bitmap[i].A = data[i * 4 + 3]; + } + // memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 7fa456c4dc..aef7ed1364 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -46,13 +47,11 @@ void displayDepth(const vpImage &depthImage, int main() { - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 480, 640, 0.01, 1.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 1.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); const std::string objectName = "object"; - - std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS)); std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); @@ -72,29 +71,24 @@ int main() NodePath object = renderer.loadObject(objectName, "/home/sfelton/cube.bam"); std::cout << "Adding node to scene" <set_specular(LColor(1.0, 1.0, 1.0, 1.0)); - mat->set_base_color(LColor(1.0)); - mat->set_shininess(5.0); - mat->set_metallic(5.0); - object.set_material(mat); + renderer.addNodeToScene(object); + rgbRenderer->getRenderRoot().set_shader_auto(100); + // PT(PointLight) plight = new PointLight("sun"); + // plight->set_color(LColor(1.0, 1.0, 1.0, 1)); + // NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); + // plnp.set_pos(0.4, -0.5, 0.1); + // rgbRenderer->getRenderRoot().set_light(plnp); - renderer.addNodeToScene(object); - // PT(DirectionalLight) d_light; - // d_light = new DirectionalLight("my d_light"); - // d_light->set_color(LColor(10.0, 1.0, 1.0, 1)); - // d_light->set_direction(LVector3(0.5, 0.5, 0.0).normalized()); // NodePath dlnp = rgbRenderer->getRenderRoot().attach_new_node(d_light); - // d_light->set_point(LPoint3(-5, -5, 0)); - // // dlnp.set_hpr(-30, -60, 0); + // dlnp.set_hpr(-30, -60, 0); // rgbRenderer->getRenderRoot().set_light(dlnp); - PT(AmbientLight) a_light = new AmbientLight("my a_light"); - a_light->set_color(LColor(1.0, 5.0, 0.0, 1.0)); - NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(a_light); - rgbRenderer->getRenderRoot().set_light(alnp); + // PT(AmbientLight) a_light = new AmbientLight("my a_light"); + // a_light->set_color(LColor(1.0, 1.0, 1.0, 1.0)); + // NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(a_light); + // rgbRenderer->getRenderRoot().set_light(alnp); std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, -0.4, 0.0, 0.0, 0.0, 0.0)); @@ -139,7 +133,7 @@ int main() renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage, depthImage); renderer.getRenderer()->getRender(colorImage); - + std::cout << colorImage.getMaxValue() << std::endl; const double beforeConvert = vpTime::measureTimeMs(); displayNormals(normalsImage, normalDisplayImage); From dcbe0b2fa39372944fc617c433c6d55160597b08 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 19 Apr 2024 18:11:26 +0200 Subject: [PATCH 15/65] Disable auto_shader --- .../src/panda3d-simulator/vpPanda3DBaseRenderer.cpp | 1 - tutorial/ar/tutorial-panda3d-renderer.cpp | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index b75b55eeff..0011a0f131 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -41,7 +41,6 @@ void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr frame void vpPanda3DBaseRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_renderRoot.set_shader_auto(); } void vpPanda3DBaseRenderer::setupCamera() diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index aef7ed1364..9e605d8d77 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -73,12 +73,12 @@ int main() renderer.addNodeToScene(object); - rgbRenderer->getRenderRoot().set_shader_auto(100); - // PT(PointLight) plight = new PointLight("sun"); - // plight->set_color(LColor(1.0, 1.0, 1.0, 1)); - // NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); - // plnp.set_pos(0.4, -0.5, 0.1); - // rgbRenderer->getRenderRoot().set_light(plnp); + // rgbRenderer->getRenderRoot().set_shader_auto(100); + PT(PointLight) plight = new PointLight("sun"); + plight->set_color(LColor(1.0, 1.0, 1.0, 1)); + NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); + plnp.set_pos(0.4, -0.5, 0.1); + rgbRenderer->getRenderRoot().set_light(plnp); From a37defc627ca36a40f3fa578303a94daed863316 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 22 Apr 2024 10:06:59 +0200 Subject: [PATCH 16/65] Copy data folder --- tutorial/ar/CMakeLists.txt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tutorial/ar/CMakeLists.txt b/tutorial/ar/CMakeLists.txt index 1b7c1c7a0a..d8591335f6 100644 --- a/tutorial/ar/CMakeLists.txt +++ b/tutorial/ar/CMakeLists.txt @@ -9,11 +9,8 @@ set(tutorial_cpp tutorial-panda3d-renderer.cpp ) -# file(GLOB tutorial_data "*.jpg") - visp_set_source_file_compile_flag(tutorial-panda3d-renderer.cpp -Wno-extra -Wno-unused-parameter -Wno-unused-variable) - foreach(cpp ${tutorial_cpp}) visp_add_target(${cpp}) if(COMMAND visp_add_dependency) @@ -21,9 +18,5 @@ foreach(cpp ${tutorial_cpp}) endif() endforeach() - - -# Copy the data files to the same location than the target -# foreach(data ${tutorial_data}) -# visp_copy_data(tutorial-hsv-segmentation-basic.cpp ${data}) -# endforeach() +# Copy the data folder to the same location than the target +visp_copy_dir(tutorial-panda3d-renderer "${CMAKE_CURRENT_SOURCE_DIR}" data) From 351ebd31f9f3a82f28572faf918ad1f1e2e20cc6 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 22 Apr 2024 10:08:37 +0200 Subject: [PATCH 17/65] Better display positionning and click to quit --- tutorial/ar/tutorial-panda3d-renderer.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 9e605d8d77..238ace5126 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,3 +1,4 @@ +//! \example tutorial-panada3d-renderer.cpp #include #include #if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) @@ -80,8 +81,6 @@ int main() plnp.set_pos(0.4, -0.5, 0.1); rgbRenderer->getRenderRoot().set_light(plnp); - - // NodePath dlnp = rgbRenderer->getRenderRoot().attach_new_node(d_light); // dlnp.set_hpr(-30, -60, 0); // rgbRenderer->getRenderRoot().set_light(dlnp); @@ -116,11 +115,12 @@ int main() #endif DisplayCls dNormals(normalDisplayImage, 0, 0, "normals in world space"); - DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, 0, "normals in camera space"); - DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth(), 0, "depth"); - DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2, 0, "color"); + DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, renderParams.getImageHeight() + 80, "normals in camera space"); + DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth() + 80, 0, "depth"); + DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2 + 90, 0, "color"); - while (true) { + bool end = false; + while (!end) { float near = 0, far = 0; const double beforeComputeBB = vpTime::measureTimeMs(); rgbRenderer->computeNearAndFarPlanesFromNode(objectName, near, far); @@ -140,6 +140,10 @@ int main() displayNormals(cameraNormalsImage, cameraNormalDisplayImage); displayDepth(depthImage, depthDisplayImage); vpDisplay::display(colorImage); + vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); + if (vpDisplay::getClick(colorImage, false)) { + end = true; + } vpDisplay::flush(colorImage); const double afterAll = vpTime::measureTimeMs(); From c5ed02206cd90a5a18e58d880647604fd984dfc1 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 22 Apr 2024 10:09:06 +0200 Subject: [PATCH 18/65] Improve Panda3d detection and version retrieval --- CMakeLists.txt | 5 +-- cmake/FindMyPanda3D.cmake | 93 ++++++++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6d0a5b8c8..1bb66f0fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1722,15 +1722,14 @@ status(" \\- Use Coin3D:" USE_COIN3D THEN "yes (ver ${COIN3D_VE status(" \\- Use SoWin:" USE_SOWIN THEN "yes" ELSE "no") status(" \\- Use SoXt:" USE_SOXT THEN "yes" ELSE "no") if(USE_SOQT AND SoQt_FOUND) -status(" \\- Use SoQt:" USE_SOQT AND SoQt_FOUND THEN "yes (ver ${SoQt_VERSION})" ELSE "no") +status(" \\- Use SoQt:" USE_SOQT AND SoQt_FOUND THEN "yes (ver ${SoQt_VERSION})" ELSE "no") else() status(" \\- Use SoQt:" USE_SOQT THEN "yes" ELSE "no") endif() status(" \\- Use Qt5:" USE_SOQT AND SoQt_FOUND THEN "yes" ELSE "no") status(" \\- Use Qt4:" USE_QT AND DESIRED_QT_VERSION MATCHES 4 THEN "yes" ELSE "no") status(" \\- Use Qt3:" USE_QT AND DESIRED_QT_VERSION MATCHES 3 THEN "yes" ELSE "no") - -status(" Panda3D:" USE_PANDA3D AND Panda3D_FOUND THEN "yes" ELSE "no") +status(" Panda3D:" USE_PANDA3D THEN "yes (ver ${Panda3D_VERSION_STRING})" ELSE "no") status("") status(" Media I/O: ") diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index 0433802175..e7f2ccc147 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -1,8 +1,60 @@ -set(Panda3D_DIR) -if(${Panda3D_DIR}) -else() - set(Panda3D_DIR "$ENV{Panda3D_DIR}") -endif() +############################################################################# +# +# ViSP, open source Visual Servoing Platform software. +# Copyright (C) 2005 - 2024 by Inria. All rights reserved. +# +# This software is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# See the file LICENSE.txt at the root directory of this source +# distribution for additional information about the GNU GPL. +# +# For using ViSP with software that can not be combined with the GNU +# GPL, please contact Inria about acquiring a ViSP Professional +# Edition License. +# +# See https://visp.inria.fr for more information. +# +# This software was developed at: +# Inria Rennes - Bretagne Atlantique +# Campus Universitaire de Beaulieu +# 35042 Rennes Cedex +# France +# +# If you have questions regarding the use of this file, please contact +# Inria at visp@inria.fr +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# Description: +# Try to find OpenCV framework. +# +# Panda3D_FOUND +# Panda3D_INCLUDE_DIRS +# Panda3D_LIBRARIES +# +############################################################################# + +set(PANDA3D_INCLUDE_SEARCH_PATHS + $ENV{Panda3D_DIR}/include + $ENV{Panda3D_DIR}/built/include + ${Panda3D_DIR}/include + ${Panda3D_DIR}/built/include + /usr/include/panda3d + /usr/local/include/panda3d +) + +set(PANDA3D_LIBRARIES_SEARCH_PATHS + $ENV{Panda3D_DIR}/lib + $ENV{Panda3D_DIR}/built/lib + ${Panda3D_DIR}/lib + ${Panda3D_DIR}/built/lib + /usr/lib/panda3d + /usr/local/lib/panda3d + /usr/lib/x86_64-linux-gnu/panda3d +) set(PANDA3D_LIBS panda p3framework pandaexpress @@ -12,39 +64,30 @@ set(PANDA3D_LIBS #pandaai pandafx pandaphysics pandaskel ) -# First option: built with makepanda -# Panda3D_DIR should point to the "built" directory generated by makepanda -if(Panda3D_DIR) - set(LIBRARY_PATHS "${Panda3D_DIR}/lib") - set(INCLUDE_PATHS "${Panda3D_DIR}/include") - set(Panda3D_BINARY_DIR "${Panda3D_DIR}/bin") - -else() # Otherwise, check for installed Panda3D - set(LIBRARY_PATHS "/usr/lib" "/usr/lib/panda3d" "/usr/lib/x86_64-linux-gnu/panda3d") - set(INCLUDE_PATHS "/usr/include/panda3d") - set(Panda3D_BINARY_DIR "/usr/bin") -endif() - # Fetch all libraries set(Panda3D_LIBRARIES "") set(ALL_LIBS_FOUND TRUE) foreach(lib_name ${PANDA3D_LIBS}) - find_library(Panda3D_${lib_name}_LIBRARY ${lib_name} PATHS ${LIBRARY_PATHS}) - if(Panda3D_${lib_name}_LIBRARY EQUAL "Panda3D_${lib_name}_LIBRARY-NOTFOUND") + find_library(Panda3D_${lib_name}_LIBRARY ${lib_name} PATHS ${PANDA3D_LIBRARIES_SEARCH_PATHS}) + + if(NOT Panda3D_${lib_name}_LIBRARY) set(ALL_LIBS_FOUND FALSE) else() - list(APPEND Panda3D_LIBRARIES "${Panda3D_${lib_name}_LIBRARY}") + list(APPEND Panda3D_LIBRARIES ${Panda3D_${lib_name}_LIBRARY}) endif() + mark_as_advanced(Panda3D_${lib_name}_LIBRARY) endforeach() -find_path(Panda3D_INCLUDE_DIRS panda.h PATHS ${INCLUDE_PATHS}) +find_path(Panda3D_INCLUDE_DIRS panda.h PATHS ${PANDA3D_INCLUDE_SEARCH_PATHS}) include(FindPackageHandleStandardArgs) -# Handle the QUIETLY and REQUIRED arguments and set the DUMMYSDK_FOUND to TRUE +# Handle the QUIETLY and REQUIRED arguments and set the Panda3D_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(Panda3D DEFAULT_MSG ALL_LIBS_FOUND Panda3D_INCLUDE_DIRS) -if(Panda3D_FOUND) - mark_as_advanced(Panda3D_LIBRARIES Panda3D_INCLUDE_DIRS Panda3D_BINARY_DIR) +if(Panda3D_FOUND) + vp_parse_header2(Panda3D "${Panda3D_INCLUDE_DIRS}/pandaVersion.h" PANDA_VERSION_STR) endif() + +mark_as_advanced(Panda3D_INCLUDE_DIRS) From 0cc3133c772536169d1c54558ff0025c1ea89bfc Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 22 Apr 2024 10:09:32 +0200 Subject: [PATCH 19/65] Introduce copyright and remove empty lines --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 35 ++++++++++++++++--- .../visp3/ar/vpPanda3DGeometryRenderer.h | 32 ++++++++++++++++- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 32 ++++++++++++++++- .../visp3/ar/vpPanda3DRenderParameters.h | 33 +++++++++++++++-- .../include/visp3/ar/vpPanda3DRendererSet.h | 35 ++++++++++++++++--- .../vpPanda3DBaseRenderer.cpp | 33 +++++++++++++++-- .../vpPanda3DGeometryRenderer.cpp | 33 +++++++++++++++-- .../vpPanda3DRGBRenderer.cpp | 33 +++++++++++++++-- .../vpPanda3DRenderParameters.cpp | 34 ++++++++++++++++-- 9 files changed, 280 insertions(+), 20 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index a0fc62572b..7f537c7ada 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPanda3DBaseRenderer_h #define vpPanda3DBaseRenderer_h @@ -10,7 +41,6 @@ #include #include - /** * @brief Base class for a panda3D renderer. This class handles basic functionalities, * such as loading object, changing camera parameters. @@ -171,7 +201,6 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ void setAbortOnPandaError(bool abort); - protected: /** @@ -202,9 +231,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer NodePath m_renderRoot; //! Node containing all the objects and the camera for this renderer PT(Camera) m_camera; NodePath m_cameraPath; //! NodePath of the camera - }; - #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index a3706b565c..f43ab9a52c 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPanda3DGeometryRenderer_h #define vpPanda3DGeometryRenderer_h @@ -53,6 +84,5 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer }; - #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 87589afbe8..a2a5ae3c16 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPanda3DRGBRenderer_h #define vpPanda3DRGBRenderer_h @@ -34,6 +65,5 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer }; - #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h index a9cbe3eba8..870f50f245 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPanda3DRenderParameters_h #define vpPanda3DRenderParameters_h @@ -6,7 +37,6 @@ #if defined(VISP_HAVE_PANDA3D) #include - class Camera; /** @@ -65,7 +95,6 @@ class VISP_EXPORT vpPanda3DRenderParameters unsigned int getImageWidth() const { return m_width; } unsigned int getImageHeight() const { return m_height; } - /** * @brief Set the image resolution. * When this object is given to a vpPanda3DBaseRenderer, diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index c810749752..490b1b9b89 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPanda3DRendererSet_h #define vpPanda3DRendererSet_h #include @@ -6,7 +37,6 @@ #include - #include class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer @@ -41,7 +71,6 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer void setupCamera() vp_override { } - void setCameraPose(const vpHomogeneousMatrix &wTc) vp_override { for (std::shared_ptr &renderer: m_subRenderers) { @@ -81,7 +110,6 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer throw vpException(vpException::badValue, "NodePath getNodePose is not supported in renderer set, prefer the string version"); } - void addNodeToScene(const NodePath &object) vp_override { for (std::shared_ptr &renderer: m_subRenderers) { @@ -89,7 +117,6 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer } } - void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override { m_renderParameters = params; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 0011a0f131..21b8cdf583 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #include #if defined(VISP_HAVE_PANDA3D) @@ -75,7 +106,6 @@ vpHomogeneousMatrix vpPanda3DBaseRenderer::getCameraPose() return getNodePose(m_cameraPath); } - void vpPanda3DBaseRenderer::setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) { NodePath object = m_renderRoot.find(name); @@ -123,7 +153,6 @@ void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &n far = std::max(near, maxP.get_y()); } - NodePath vpPanda3DBaseRenderer::loadObject(const std::string &nodeName, const std::string &modelPath) { NodePath model = m_window->load_model(m_framework->get_models(), modelPath); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 27d8d5c4e9..bd7e7b894a 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -1,10 +1,40 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #include #if defined(VISP_HAVE_PANDA3D) #include - const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( #version 140 in vec3 p3d_Normal; @@ -78,7 +108,6 @@ void vpPanda3DGeometryRenderer::setupScene() m_renderRoot.set_shader(shader); } - void vpPanda3DGeometryRenderer::setupRenderTarget() { FrameBufferProperties fbp; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 3377994046..81b2355d1a 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -1,8 +1,38 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #include #if defined(VISP_HAVE_PANDA3D) - void vpPanda3DRGBRenderer::getRender(vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); @@ -22,7 +52,6 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const // memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); } - void vpPanda3DRGBRenderer::setupRenderTarget() { FrameBufferProperties fbp; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp index f2a6467633..9a1e16d9dc 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp @@ -1,8 +1,38 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #include #if defined(VISP_HAVE_PANDA3D) - #include #include @@ -33,6 +63,6 @@ void vpPanda3DRenderParameters::setupPandaCamera(Camera *camera) lens->set_film_size(m_width, m_height); lens->set_film_offset(m_width * 0.5 - cx, m_height * 0.5 - cy); camera->set_lens(lens); - } + #endif From c66fc3b4084a127e2ffc4c194fe3406a75a536d7 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 22 Apr 2024 10:12:46 +0200 Subject: [PATCH 20/65] Fix doxygen warnings --- modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h | 1 + tutorial/ar/tutorial-panda3d-renderer.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 7f537c7ada..716a8f6c54 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -75,6 +75,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @brief * * @param framework + * @param window */ void initFromParent(std::shared_ptr framework, PT(WindowFramework) window); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 238ace5126..10c4494513 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,4 +1,4 @@ -//! \example tutorial-panada3d-renderer.cpp +//! \example tutorial-panda3d-renderer.cpp #include #include #if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) From 6e6895d3c782d104a02c5b5138b489cd8b8ae6dd Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 16:56:55 +0200 Subject: [PATCH 21/65] Improve warnings filter in cmake, fix depth and normal shaders to use correct coordinate systems --- modules/ar/CMakeLists.txt | 14 +- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 1 + .../visp3/ar/vpPanda3DGeometryRenderer.h | 13 ++ .../include/visp3/ar/vpPanda3DRendererSet.h | 175 ++++++++---------- .../vpPanda3DBaseRenderer.cpp | 12 +- .../vpPanda3DGeometryRenderer.cpp | 70 +++++-- .../vpPanda3DRGBRenderer.cpp | 16 +- .../vpPanda3DRendererSet.cpp | 135 ++++++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 15 +- 9 files changed, 321 insertions(+), 130 deletions(-) create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 56dbca39eb..7a5eb94cde 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -192,12 +192,14 @@ endif() if(USE_PANDA3D) set(PANDA3D_CXX_FLAGS -Wno-unused-parameter -Wno-unused-variable -Wno-extra) - vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp ${PANDA3D_CXX_FLAGS}) - vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DRGBRenderer.cpp ${PANDA3D_CXX_FLAGS}) - vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DBaseRenderer.cpp ${PANDA3D_CXX_FLAGS}) - vp_set_source_file_compile_flag(src/panda3d-simulator/vpPanda3DRenderParameters.cpp ${PANDA3D_CXX_FLAGS}) - - + set(PANDA3D_MODULE_SOURCES + vpPanda3DBaseRenderer.cpp vpPanda3DGeometryRenderer.cpp + vpPanda3DRGBRenderer.cpp vpPanda3DRenderParameters.cpp + vpPanda3DRendererSet.cpp + ) + foreach(panda_src_name ${PANDA3D_MODULE_SOURCES}) + vp_set_source_file_compile_flag(src/panda3d-simulator/${panda_src_name} ${PANDA3D_CXX_FLAGS}) + endforeach() endif() vp_module_include_directories(${opt_incs}) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 716a8f6c54..4e07b2cf2c 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -201,6 +201,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @param abort whether to abort (true) or display a message when an assertion fails. */ void setAbortOnPandaError(bool abort); + void setForcedInvertTextures(bool invert); protected: diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index f43ab9a52c..7f304c979a 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -69,6 +69,19 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer */ void getRender(vpImage &colorData, vpImage &depth) const; + /** + * @brief Get render results into ViSP readable structures. This version only retrieves the normal data + * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + */ + void getRender(vpImage &colorData) const; + /** + * @brief Get render results into ViSP readable structures. This version only retrieves the depth data. + * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + */ + void getRender(vpImage &depth) const; + + + protected: void setupScene() vp_override; void setupRenderTarget() vp_override; diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index 490b1b9b89..d27df7e26c 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -42,103 +42,79 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer { public: - vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") - { - m_renderParameters = renderParameters; - } - - void initFramework(bool showWindow) vp_override - { - if (m_framework.use_count() > 0) { - throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); - } - m_framework = std::shared_ptr(new PandaFramework()); - m_framework->open_framework(); - WindowProperties winProps; - winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); - int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; - m_window = m_framework->open_window(winProps, flags); - if (m_window == nullptr) { - throw vpException(vpException::fatalError, "Could not open Panda3D window (hidden or visible)"); - } - m_window->set_background_type(WindowFramework::BackgroundType::BT_black); - for (std::shared_ptr &renderer: m_subRenderers) { - renderer->initFromParent(m_framework, m_window); - } - } - - void setupScene() vp_override { } - - void setupCamera() vp_override { } - - void setCameraPose(const vpHomogeneousMatrix &wTc) vp_override - { - for (std::shared_ptr &renderer: m_subRenderers) { - renderer->setCameraPose(wTc); - } - } - - vpHomogeneousMatrix getCameraPose() vp_override - { - if (m_subRenderers.size() == 0) { - throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); - } - return m_subRenderers[0]->getCameraPose(); - } - - void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) vp_override - { - for (std::shared_ptr &renderer: m_subRenderers) { - renderer->setNodePose(name, wTo); - } - } - - void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) vp_override - { - throw vpException(vpException::badValue, "NodePath setNodePose is not supported in renderer set, prefer the string version"); - } - - vpHomogeneousMatrix getNodePose(const std::string &name) vp_override - { - if (m_subRenderers.size() == 0) { - throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); - } - return m_subRenderers[0]->getNodePose(name); - } - vpHomogeneousMatrix getNodePose(NodePath &object) vp_override - { - throw vpException(vpException::badValue, "NodePath getNodePose is not supported in renderer set, prefer the string version"); - } - - void addNodeToScene(const NodePath &object) vp_override - { - for (std::shared_ptr &renderer: m_subRenderers) { - renderer->addNodeToScene(object); - } - } - - void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override - { - m_renderParameters = params; - for (std::shared_ptr &renderer: m_subRenderers) { - renderer->setRenderParameters(m_renderParameters); - } - } - - void addSubRenderer(std::shared_ptr renderer) - { - for (std::shared_ptr &otherRenderer: m_subRenderers) { - if (renderer->getName() == otherRenderer->getName()) { - throw vpException(vpException::badValue, "Cannot have two subrenderers with the same name"); - } - } - m_subRenderers.push_back(renderer); - renderer->setRenderParameters(m_renderParameters); - if (m_framework != nullptr) { - renderer->initFromParent(m_framework, m_window); - renderer->setCameraPose(getCameraPose()); - } - } + vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters); + virtual ~vpPanda3DRendererSet(); + + /** + * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers + * + * @param showWindow Whether a window should be shown + */ + void initFramework(bool showWindow) vp_override; + + /** + * @brief Set the pose of the camera, using the ViSP convention. This change is propagated to all subrenderers + * + * @param wTc Pose of the camera + */ + void setCameraPose(const vpHomogeneousMatrix &wTc) vp_override; + + /** + * @brief Retrieve the pose of the camera. As this renderer contains multiple other renderers. + * + * \warning It is assumed that all the sub renderers are synchronized (i.e., the setCameraPose of this renderer was called before calling this method). + * Otherwise, you may get incoherent results. + * + * @return the pose of the camera using the ViSP convention + */ + vpHomogeneousMatrix getCameraPose() vp_override; + + /** + * @brief Set the pose of an object for all the subrenderers. The pose is specified using the ViSP convention + * This method may fail if a subrenderer does not have a node with the given name. + * + * \warning This method may fail if a subrenderer does not have a node with the given name. It is assumed that the scenes are synchronized + * + * @param name + * @param wTo + */ + void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) vp_override; + + /** + * @brief This method is not supported for this renderer type. Use the std::string version + * + * \throws vpException, as this method is not supported + * @param object + * @param wTo + */ + void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) vp_override; + + /** + * @brief Retrieve the pose of a scene node. The pose is in the world frame, using a ViSP convention. + * + * \see the base method + * + * \warning It is assumed that all the sub renderers are synchronized (i.e., the setNodePose of this renderer was called before calling this method). + * Otherwise, you may get incoherent results. + * @param name name of the node + * @return vpHomogeneousMatrix the pose of the node in the world frame + */ + vpHomogeneousMatrix getNodePose(const std::string &name) vp_override; + + /** + * @brief This method is not supported for this renderer type. Use the std::string version + * + * \throws vpException, as this method is not supported + * @param object + * @param wTo + */ + vpHomogeneousMatrix getNodePose(NodePath &object) vp_override; + + void addNodeToScene(const NodePath &object) vp_override; + + void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; + + void addSubRenderer(std::shared_ptr renderer); template std::shared_ptr getRenderer() @@ -166,6 +142,11 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer return nullptr; } +protected: + void setupScene() vp_override { } + + void setupCamera() vp_override { } + private: std::vector> m_subRenderers; }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 21b8cdf583..00e9692786 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -66,7 +66,6 @@ void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr frame setupScene(); setupCamera(); setupRenderTarget(); - //m_window->get_display_region_3d()->set_camera(m_cameraPath); } void vpPanda3DBaseRenderer::setupScene() @@ -187,4 +186,15 @@ void vpPanda3DBaseRenderer::setAbortOnPandaError(bool abort) } } +void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) +{ + if (invert) { + load_prc_file_data("", "copy-texture-inverted 1"); + } + else { + load_prc_file_data("", "copy-texture-inverted 0"); + } + +} + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index bd7e7b894a..f6698e83d3 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -37,43 +37,63 @@ const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( #version 140 + in vec3 p3d_Normal; +in vec3 p3d_Vertex; + varying vec3 oNormal; uniform mat3 p3d_NormalMatrix; +uniform mat4 p3d_ModelViewMatrix; + + +varying float distToCamera; + void main() { gl_Position = ftransform(); // View space is Z-up right handed, flip z and y oNormal = p3d_NormalMatrix * normalize(p3d_Normal); - //oNormal.yz = -oNormal.zy; + oNormal.yz = oNormal.zy; + oNormal.y = -oNormal.y; + vec4 cs_position = p3d_ModelViewMatrix * vec4(p3d_Vertex, 1.0); + distToCamera = -cs_position.z; } )shader"; const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_WORLD = R"shader( -#version 120 +#version 140 +in vec3 p3d_Normal; +in vec3 p3d_Vertex; +uniform mat4 p3d_ModelViewMatrix; varying vec3 oNormal; +varying float distToCamera; + void main() { gl_Position = ftransform(); - oNormal = normalize(gl_Normal); + oNormal = normalize(p3d_Normal); + oNormal.yz = oNormal.zy; + oNormal.y = -oNormal.y; + vec4 cs_position = p3d_ModelViewMatrix * vec4(p3d_Vertex, 1.0); + distToCamera = -cs_position.z; } )shader"; const char *vpPanda3DGeometryRenderer::SHADER_FRAG_NORMAL_AND_DEPTH = R"shader( -#version 120 +#version 140 varying vec3 oNormal; +varying float distToCamera; + void main() { vec3 n = normalize(oNormal); //if (!gl_FrontFacing) //n = -n; - float fDepth = gl_FragCoord.z; - - gl_FragColor = vec4(n, fDepth); + gl_FragColor = vec4(n, distToCamera); } )shader"; @@ -122,13 +142,14 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() // Don't open a window - force it to be an offscreen buffer. int flags = GraphicsPipe::BF_refuse_window; - - GraphicsEngine *engine = m_window->get_graphics_output()->get_engine(); - GraphicsPipe *pipe = m_window->get_graphics_output()->get_pipe(); + GraphicsOutput *windowOutput = m_window->get_graphics_output(); + GraphicsEngine *engine = windowOutput->get_engine(); + GraphicsPipe *pipe = windowOutput->get_pipe(); m_normalDepthBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, - m_window->get_graphics_output()->get_gsg(), - m_window->get_graphics_output()); + windowOutput->get_gsg(), + windowOutput); m_normalDepthTexture = new Texture(); + m_normalDepthBuffer->set_inverted(windowOutput->get_gsg()->get_copy_texture_inverted()); fbp.setup_color_texture(m_normalDepthTexture); m_normalDepthBuffer->add_render_texture(m_normalDepthTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); m_normalDepthBuffer->set_clear_color(LColor(0.f)); @@ -145,7 +166,7 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_ram_image().front())); //#pragma omp parallel for simd - for (unsigned int i = 0; i < normals.getRows() * normals.getCols(); ++i) { + for (unsigned int i = 0; i < normals.getSize(); ++i) { normals.bitmap[i].B = (data[i * 4]); normals.bitmap[i].G = (data[i * 4 + 1]); normals.bitmap[i].R = (data[i * 4 + 2]); @@ -153,4 +174,27 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImage &normals) const +{ + normals.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + +//#pragma omp parallel for simd + for (unsigned int i = 0; i < normals.getSize(); ++i) { + normals.bitmap[i].B = (data[i * 4]); + normals.bitmap[i].G = (data[i * 4 + 1]); + normals.bitmap[i].R = (data[i * 4 + 2]); + } +} + +void vpPanda3DGeometryRenderer::getRender(vpImage &depth) const +{ + depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); +//#pragma omp parallel for simd + for (unsigned int i = 0; i < depth.getSize(); ++i) { + depth.bitmap[i] = (data[i * 4 + 3]); + } +} + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 81b2355d1a..cfa4fb692d 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -66,12 +66,16 @@ void vpPanda3DRGBRenderer::setupRenderTarget() // Don't open a window - force it to be an offscreen buffer. int flags = GraphicsPipe::BF_refuse_window; - - GraphicsEngine *engine = m_window->get_graphics_output()->get_engine(); - GraphicsPipe *pipe = m_window->get_graphics_output()->get_pipe(); - m_colorBuffer = engine->make_output(pipe, "Color Buffer", -100, fbp, win_prop, flags, - m_window->get_graphics_output()->get_gsg(), - m_window->get_graphics_output()); + GraphicsOutput *windowOutput = m_window->get_graphics_output(); + GraphicsEngine *engine = windowOutput->get_engine(); + GraphicsStateGuardian *gsg = windowOutput->get_gsg(); + GraphicsPipe *pipe = windowOutput->get_pipe(); + m_colorBuffer = engine->make_output(pipe, "Color Buffer", -100, + fbp, win_prop, flags, + gsg, windowOutput); + std::cout << "GSG inverted = " << gsg->get_copy_texture_inverted() << std::endl; + m_colorBuffer->set_inverted(gsg->get_copy_texture_inverted()); + std::cout << "BUFFER inverted = " << m_colorBuffer->get_inverted() << std::endl; m_colorTexture = new Texture(); fbp.setup_color_texture(m_colorTexture); m_colorBuffer->add_render_texture(m_colorTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp new file mode 100644 index 0000000000..a2b44dd883 --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include + +#if defined(VISP_HAVE_PANDA3D) + +vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") +{ + m_renderParameters = renderParameters; +} + +vpPanda3DRendererSet::~vpPanda3DRendererSet() +{ } + +void vpPanda3DRendererSet::initFramework(bool showWindow) +{ + if (m_framework.use_count() > 0) { + throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); + } + m_framework = std::shared_ptr(new PandaFramework()); + m_framework->open_framework(); + WindowProperties winProps; + winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); + int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + m_window = m_framework->open_window(winProps, flags); + if (m_window == nullptr) { + throw vpException(vpException::fatalError, "Could not open Panda3D window (hidden or visible)"); + } + m_window->set_background_type(WindowFramework::BackgroundType::BT_black); + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->initFromParent(m_framework, m_window); + } +} + + +void vpPanda3DRendererSet::setCameraPose(const vpHomogeneousMatrix &wTc) +{ + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setCameraPose(wTc); + } +} + +vpHomogeneousMatrix vpPanda3DRendererSet::getCameraPose() +{ + if (m_subRenderers.size() == 0) { + throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); + } + return m_subRenderers[0]->getCameraPose(); +} + +void vpPanda3DRendererSet::setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) +{ + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setNodePose(name, wTo); + } +} + +void vpPanda3DRendererSet::setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) +{ + throw vpException(vpException::badValue, "NodePath setNodePose is not supported in renderer set, prefer the string version"); +} + +vpHomogeneousMatrix vpPanda3DRendererSet::getNodePose(const std::string &name) +{ + if (m_subRenderers.size() == 0) { + throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); + } + return m_subRenderers[0]->getNodePose(name); +} + +vpHomogeneousMatrix vpPanda3DRendererSet::getNodePose(NodePath &object) +{ + throw vpException(vpException::badValue, "NodePath getNodePose is not supported in renderer set, prefer the string version"); +} + +void vpPanda3DRendererSet::addNodeToScene(const NodePath &object) +{ + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->addNodeToScene(object); + } +} + +void vpPanda3DRendererSet::setRenderParameters(const vpPanda3DRenderParameters ¶ms) +{ + m_renderParameters = params; + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->setRenderParameters(m_renderParameters); + } +} + +void vpPanda3DRendererSet::addSubRenderer(std::shared_ptr renderer) +{ + for (std::shared_ptr &otherRenderer: m_subRenderers) { + if (renderer->getName() == otherRenderer->getName()) { + throw vpException(vpException::badValue, "Cannot have two subrenderers with the same name"); + } + } + m_subRenderers.push_back(renderer); + renderer->setRenderParameters(m_renderParameters); + if (m_framework != nullptr) { + renderer->initFromParent(m_framework, m_window); + renderer->setCameraPose(getCameraPose()); + } +} + +#endif diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 10c4494513..f601f490cb 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -36,11 +36,12 @@ void displayNormals(const vpImage &normalsImage, vpDisplay::flush(normalDisplayImage); } void displayDepth(const vpImage &depthImage, - vpImage &depthDisplayImage) + vpImage &depthDisplayImage, float near, float far) { #pragma omp parallel for simd for (unsigned int i = 0; i < depthImage.getSize(); ++i) { - depthDisplayImage.bitmap[i] = static_cast(depthImage.bitmap[i] * 255.f); + float val = std::max(0.f, (depthImage.bitmap[i] - near) / (far - near)); + depthDisplayImage.bitmap[i] = static_cast(val * 255.f); } vpDisplay::display(depthDisplayImage); vpDisplay::flush(depthDisplayImage); @@ -64,19 +65,20 @@ int main() renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); + renderer.setForcedInvertTextures(true); std::cout << "Initializing framework" << std::endl; renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject(objectName, "/home/sfelton/cube.bam"); + NodePath object = renderer.loadObject(objectName, "/home/sfelton/buddha.bam"); std::cout << "Adding node to scene" <getRenderRoot().set_shader_auto(100); PT(PointLight) plight = new PointLight("sun"); - plight->set_color(LColor(1.0, 1.0, 1.0, 1)); + plight->set_color(LColor(2.0, 2.0, 2.0, 1)); NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); plnp.set_pos(0.4, -0.5, 0.1); rgbRenderer->getRenderRoot().set_light(plnp); @@ -131,14 +133,13 @@ int main() renderer.renderFrame(); const double beforeFetch = vpTime::measureTimeMs(); renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); - renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage, depthImage); + renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage); renderer.getRenderer()->getRender(colorImage); - std::cout << colorImage.getMaxValue() << std::endl; const double beforeConvert = vpTime::measureTimeMs(); displayNormals(normalsImage, normalDisplayImage); displayNormals(cameraNormalsImage, cameraNormalDisplayImage); - displayDepth(depthImage, depthDisplayImage); + displayDepth(depthImage, depthDisplayImage, near, far); vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); if (vpDisplay::getClick(colorImage, false)) { From e24e1345dcf3935ca76f92018f68fa41c91386bb Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 18:15:56 +0200 Subject: [PATCH 22/65] Use ViSP frame for object and camera positioning instead of Panda3D frame --- .../ar/include/visp3/ar/vpPanda3DBaseRenderer.h | 7 ++++++- .../panda3d-simulator/vpPanda3DBaseRenderer.cpp | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 4e07b2cf2c..19273dbefb 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -181,7 +181,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer NodePath loadObject(const std::string &nodeName, const std::string &modelPath); /** - * @brief Add a node to the scene. + * @brief Add a node to the scene. Its pose is set as the identity matrix * * @param object */ @@ -225,6 +225,11 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ virtual void setupRenderTarget() { } + const static vpHomogeneousMatrix VISP_T_PANDA; //! Homogeneous transformation matrix to convert from the Panda coordinate system (right-handed Z-up) to the ViSP coordinate system (right-handed Y-Down) + const static vpHomogeneousMatrix PANDA_T_VISP; //! Inverse of VISP_T_PANDA + + + protected: const std::string m_name; //! name of the renderer std::shared_ptr m_framework; //! Pointer to the active panda framework diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 00e9692786..de7f0d2eea 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -34,6 +34,13 @@ #if defined(VISP_HAVE_PANDA3D) #include "load_prc_file.h" +const vpHomogeneousMatrix vpPanda3DBaseRenderer::VISP_T_PANDA({ + 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -1., 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0 +}); +const vpHomogeneousMatrix vpPanda3DBaseRenderer::PANDA_T_VISP(vpPanda3DBaseRenderer::VISP_T_PANDA.inverse()); void vpPanda3DBaseRenderer::initFramework(bool showWindow) { @@ -113,8 +120,9 @@ void vpPanda3DBaseRenderer::setNodePose(const std::string &name, const vpHomogen void vpPanda3DBaseRenderer::setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) { - vpTranslationVector t = wTo.getTranslationVector(); - vpQuaternionVector q(wTo.getRotationMatrix()); + const vpHomogeneousMatrix wpTo = wTo * VISP_T_PANDA; + vpTranslationVector t = wpTo.getTranslationVector(); + vpQuaternionVector q(wpTo.getRotationMatrix()); object.set_pos(t[0], t[1], t[2]); object.set_quat(LQuaternion(q.w(), q.x(), q.y(), q.z())); } @@ -134,7 +142,7 @@ vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(NodePath &object) const LQuaternion quat = object.get_quat(); const vpTranslationVector t(pos[0], pos[1], pos[2]); const vpQuaternionVector q(quat.get_i(), quat.get_j(), quat.get_k(), quat.get_r()); - return vpHomogeneousMatrix(t, q); + return vpHomogeneousMatrix(t, q) * PANDA_T_VISP; } void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &name, float &near, float &far) @@ -165,6 +173,7 @@ void vpPanda3DBaseRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); + setNodePose(objectInScene, vpHomogeneousMatrix()); } void vpPanda3DBaseRenderer::setVerticalSyncEnabled(bool useVsync) From fd89ed9efa7f2259bda271a2d300e1c0b0a82e1a Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 18:16:10 +0200 Subject: [PATCH 23/65] add small model, add argument parsing --- tutorial/ar/data/deformed_sphere.bam | Bin 0 -> 57657 bytes tutorial/ar/tutorial-panda3d-renderer.cpp | 58 ++++++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 tutorial/ar/data/deformed_sphere.bam diff --git a/tutorial/ar/data/deformed_sphere.bam b/tutorial/ar/data/deformed_sphere.bam new file mode 100644 index 0000000000000000000000000000000000000000..4da7885825c789842bec09a9fb0e1ccd957e1ea5 GIT binary patch literal 57657 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 literal 0 HcmV?d00001 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index f601f490cb..fb789e4ec0 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,7 +1,7 @@ //! \example tutorial-panda3d-renderer.cpp #include #include -#if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) +#if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) && defined(VISP_HAVE_MODULE_IO) #include #include @@ -12,7 +12,7 @@ #include #include - +#include #include #include @@ -47,8 +47,34 @@ void displayDepth(const vpImage &depthImage, vpDisplay::flush(depthDisplayImage); } -int main() + + +int main(int argc, const char **argv) { + bool invertTexture = false; + std::string modelPathCstr; + vpParseArgv::vpArgvInfo argTable[] = + { + {"-invert", vpParseArgv::ARGV_CONSTANT_BOOL, 0, (char *)&invertTexture, + "Whether to force Texture inversion. Use this if the model is upside down."}, + {"-model", vpParseArgv::ARGV_STRING, (char *) nullptr, (char *)&modelPathCstr, + "Path to the model to load."}, + {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, + "Print the help."}, + {(char *) nullptr, vpParseArgv::ARGV_END, (char *) nullptr, (char *) nullptr, (char *) nullptr} }; + + // Read the command line options + if (vpParseArgv::parse(&argc, argv, argTable, + vpParseArgv::ARGV_NO_LEFTOVERS | + vpParseArgv::ARGV_NO_ABBREV | + vpParseArgv::ARGV_NO_DEFAULTS)) { + return (false); + } + + std::string modelPath(modelPathCstr); + if (modelPath.size() == 0) { + modelPath = "data/deformed_sphere.bam"; + } vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 1.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -65,39 +91,37 @@ int main() renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); - renderer.setForcedInvertTextures(true); + if (invertTexture) { + renderer.setForcedInvertTextures(true); + } std::cout << "Initializing framework" << std::endl; renderer.initFramework(false); std::cout << "Loading object" << std::endl; - NodePath object = renderer.loadObject(objectName, "/home/sfelton/buddha.bam"); + NodePath object = renderer.loadObject(objectName, modelPath); std::cout << "Adding node to scene" <getRenderRoot().set_shader_auto(100); + PT(AmbientLight) alight = new AmbientLight("ambient"); + alight->set_color(LColor(0.2, 0.2, 0.2, 1)); + NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); + rgbRenderer->getRenderRoot().set_light(alnp); PT(PointLight) plight = new PointLight("sun"); plight->set_color(LColor(2.0, 2.0, 2.0, 1)); NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); plnp.set_pos(0.4, -0.5, 0.1); rgbRenderer->getRenderRoot().set_light(plnp); - // NodePath dlnp = rgbRenderer->getRenderRoot().attach_new_node(d_light); - // dlnp.set_hpr(-30, -60, 0); - // rgbRenderer->getRenderRoot().set_light(dlnp); - // PT(AmbientLight) a_light = new AmbientLight("my a_light"); - // a_light->set_color(LColor(1.0, 1.0, 1.0, 1.0)); - // NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(a_light); - // rgbRenderer->getRenderRoot().set_light(alnp); - std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, -0.4, 0.0, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.5, 0.0, 0.0, 0.0)); vpImage normalsImage; vpImage cameraNormalsImage; - vpImage depthImage; + vpImage colorImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpImage normalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); vpImage cameraNormalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); @@ -150,7 +174,9 @@ int main() const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); - vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0) }), delta); + std::cout << wTo << std::endl; + + vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0), 0.0 }), delta); renderer.setNodePose(objectName, wTo * oToo); std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << beforeFetch - beforeRender << "ms" << std::endl; std::cout << "Copying to vpImage took: " << std::fixed << std::setprecision(2) << beforeConvert - beforeFetch << "ms" << std::endl; From 858afb68eaf94f376bd7dd6a973eb1c774d5e066 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 18:18:13 +0200 Subject: [PATCH 24/65] document using ViSP convention for setting and retrieving object and camera pose --- modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 19273dbefb..8fc61bc633 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -110,6 +110,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer /** * @brief Set the camera's pose. + * The pose is specified using the ViSP convention (Y-down right handed). * * @param wTc the new pose of the camera, in world frame */ @@ -117,11 +118,13 @@ class VISP_EXPORT vpPanda3DBaseRenderer /** * @brief Retrieve the camera's pose, in the world frame. + * The pose is specified using the ViSP convention (Y-down right handed). */ virtual vpHomogeneousMatrix getCameraPose(); /** * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). + * The pose is specified using the ViSP convention (Y-down right handed). * * @param name Node path to search for, from the render root. This is the object that will be modified See https://docs.panda3d.org/1.10/python/programming/scene-graph/searching-scene-graph * @param wTo Pose of the object in the world frame @@ -131,7 +134,9 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo); /** - * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). + * @brief Set the pose of a node. + * The pose is specified using the ViSP convention (Y-down right handed). + * This node can be any Panda object (light, mesh, camera). * * @param object The object for which to set the pose * @param wTo Pose of the object in the world frame @@ -139,7 +144,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo); /** - * @brief Get the pose of a Panda node, in world frame. + * @brief Get the pose of a Panda node, in world frame in the ViSP convention (Y-down right handed). * * @param name Node path to search for. \see setNodePose(const std::string &, const vpHomogeneousMatrix &) for more info * @return wTo, the pose of the object in world frame @@ -148,7 +153,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer virtual vpHomogeneousMatrix getNodePose(const std::string &name); /** - * @brief Get the pose of a Panda node, in world frame. This version of the method directly uses the Panda Nodepath. + * @brief Get the pose of a Panda node, in world frame in the ViSP convention (Y-down right handed). This version of the method directly uses the Panda Nodepath. */ virtual vpHomogeneousMatrix getNodePose(NodePath &object); From 984c775c0bd8116b463e4b0db0746ad2e8d8e8f4 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 18:35:10 +0200 Subject: [PATCH 25/65] Working on adding resizable buffers --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 18 ++++++++++++++++++ .../vpPanda3DGeometryRenderer.cpp | 7 ++++++- .../panda3d-simulator/vpPanda3DRGBRenderer.cpp | 13 +++++-------- tutorial/ar/tutorial-panda3d-renderer.cpp | 1 - 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 8fc61bc633..01674660c5 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -102,7 +102,24 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms) { + unsigned int previousH = m_renderParameters.getImageHeight(), previousW = m_renderParameters.getImageWidth(); + bool resize = previousH != params.getImageHeight() || previousW != params.getImageWidth(); + m_renderParameters = params; + + if (resize) { + for (GraphicsOutput *buffer: m_buffers) { + GraphicsBuffer *buf = dynamic_cast(buffer); + if (buf == nullptr) { + throw vpException(vpException::fatalError, "Panda3D: could not cast to GraphicsBuffer when rendering."); + } + else { + buf->set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); + } + } + } + + // If renderer is already initialize, modify camera properties if (m_camera != nullptr) { m_renderParameters.setupPandaCamera(m_camera); } @@ -243,6 +260,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer NodePath m_renderRoot; //! Node containing all the objects and the camera for this renderer PT(Camera) m_camera; NodePath m_cameraPath; //! NodePath of the camera + std::vector m_buffers; //! Set of buffers that this renderer uses. This storage contains weak refs to those buffers and should not deallocate them. }; #endif //VISP_HAVE_PANDA3D diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index f6698e83d3..254adece07 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -141,13 +141,18 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); // Don't open a window - force it to be an offscreen buffer. - int flags = GraphicsPipe::BF_refuse_window; + int flags = GraphicsPipe::BF_refuse_window | GraphicsPipe::BF_resizeable; GraphicsOutput *windowOutput = m_window->get_graphics_output(); GraphicsEngine *engine = windowOutput->get_engine(); GraphicsPipe *pipe = windowOutput->get_pipe(); m_normalDepthBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, windowOutput->get_gsg(), windowOutput); + + if (m_normalDepthBuffer == nullptr) { + throw vpException(vpException::fatalError, "Could not create geometry info buffer"); + } + m_buffers.push_back(m_normalDepthBuffer); m_normalDepthTexture = new Texture(); m_normalDepthBuffer->set_inverted(windowOutput->get_gsg()->get_copy_texture_inverted()); fbp.setup_color_texture(m_normalDepthTexture); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index cfa4fb692d..d26cb5754a 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -36,12 +36,7 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); - std::cout << "Before request" << std::endl; unsigned char *data = (unsigned char *)(&(m_colorTexture->get_ram_image().front())); - std::cout << m_colorTexture->get_format() << std::endl; - // CPTA_uchar p((const unsigned char *)I.bitmap, ((const unsigned char *)I.bitmap) + I.getSize() * 4); - // m_colorTexture->set_ram_image_as(p, "rgba"); - std::cout << "After request" << std::endl; // BGRA order in panda3d for (unsigned int i = 0; i < I.getSize(); ++i) { I.bitmap[i].B = data[i * 4]; @@ -65,7 +60,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); // Don't open a window - force it to be an offscreen buffer. - int flags = GraphicsPipe::BF_refuse_window; + int flags = GraphicsPipe::BF_refuse_window | GraphicsPipe::BF_resizeable; GraphicsOutput *windowOutput = m_window->get_graphics_output(); GraphicsEngine *engine = windowOutput->get_engine(); GraphicsStateGuardian *gsg = windowOutput->get_gsg(); @@ -73,9 +68,11 @@ void vpPanda3DRGBRenderer::setupRenderTarget() m_colorBuffer = engine->make_output(pipe, "Color Buffer", -100, fbp, win_prop, flags, gsg, windowOutput); - std::cout << "GSG inverted = " << gsg->get_copy_texture_inverted() << std::endl; + if (m_colorBuffer == nullptr) { + throw vpException(vpException::fatalError, "Could not create color buffer"); + } + m_buffers.push_back(m_colorBuffer); m_colorBuffer->set_inverted(gsg->get_copy_texture_inverted()); - std::cout << "BUFFER inverted = " << m_colorBuffer->get_inverted() << std::endl; m_colorTexture = new Texture(); fbp.setup_color_texture(m_colorTexture); m_colorBuffer->add_render_texture(m_colorTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index fb789e4ec0..d688d976d0 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -174,7 +174,6 @@ int main(int argc, const char **argv) const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); - std::cout << wTo << std::endl; vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0), 0.0 }), delta); renderer.setNodePose(objectName, wTo * oToo); From ca6b5374dc128d84fdde1b25b02b77c69f22c870 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 22 Apr 2024 22:22:27 +0200 Subject: [PATCH 26/65] Add step by step option --- .../vpPanda3DBaseRenderer.cpp | 1 - tutorial/ar/tutorial-panda3d-renderer.cpp | 36 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index de7f0d2eea..87dcd48172 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -93,7 +93,6 @@ void vpPanda3DBaseRenderer::setupCamera() void vpPanda3DBaseRenderer::renderFrame() { m_framework->get_graphics_engine()->render_frame(); - m_framework->get_graphics_engine()->sync_frame(); } void vpPanda3DBaseRenderer::setCameraPose(const vpHomogeneousMatrix &wTc) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index d688d976d0..9ac6ed03bf 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -52,6 +52,7 @@ void displayDepth(const vpImage &depthImage, int main(int argc, const char **argv) { bool invertTexture = false; + bool stepByStep = false; std::string modelPathCstr; vpParseArgv::vpArgvInfo argTable[] = { @@ -59,6 +60,9 @@ int main(int argc, const char **argv) "Whether to force Texture inversion. Use this if the model is upside down."}, {"-model", vpParseArgv::ARGV_STRING, (char *) nullptr, (char *)&modelPathCstr, "Path to the model to load."}, + {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, + "Show frames step by step."}, + {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, "Print the help."}, {(char *) nullptr, vpParseArgv::ARGV_END, (char *) nullptr, (char *) nullptr, (char *) nullptr} }; @@ -146,6 +150,7 @@ int main(int argc, const char **argv) DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2 + 90, 0, "color"); bool end = false; + std::vector renderTime, fetchTime, displayTime; while (!end) { float near = 0, far = 0; const double beforeComputeBB = vpTime::measureTimeMs(); @@ -153,12 +158,16 @@ int main(int argc, const char **argv) renderParams.setClippingDistance(near, far); renderer.setRenderParameters(renderParams); std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; + const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); + const double beforeFetch = vpTime::measureTimeMs(); + renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage); renderer.getRenderer()->getRender(colorImage); + const double beforeConvert = vpTime::measureTimeMs(); displayNormals(normalsImage, normalDisplayImage); @@ -166,21 +175,38 @@ int main(int argc, const char **argv) displayDepth(depthImage, depthDisplayImage, near, far); vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); + if (stepByStep) { + vpDisplay::displayText(colorImage, 50, 15, "Next frame: space", vpColor::red); + } if (vpDisplay::getClick(colorImage, false)) { end = true; } vpDisplay::flush(colorImage); + const double endDisplay = vpTime::measureTimeMs(); + renderTime.push_back(beforeFetch - beforeRender); + fetchTime.push_back(beforeConvert - beforeFetch); + displayTime.push_back(endDisplay - beforeConvert); + if (stepByStep) { + std::string s; + bool next = false; + while (!next) { + vpDisplay::getKeyboardEvent(colorImage, s, true); + if (s == " ") { + next = true; + } + } + } const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); - vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0), 0.0 }), delta); renderer.setNodePose(objectName, wTo * oToo); - std::cout << "Rendering took: " << std::fixed << std::setprecision(2) << beforeFetch - beforeRender << "ms" << std::endl; - std::cout << "Copying to vpImage took: " << std::fixed << std::setprecision(2) << beforeConvert - beforeFetch << "ms" << std::endl; - std::cout << "display took: " << std::fixed << std::setprecision(2) << afterAll - beforeConvert << "ms" << std::endl; - + } + if (renderTime.size() > 0) { + std::cout << "Render time: " << vpMath::getMean(renderTime) << "ms +- " << vpMath::getStdev(renderTime) << "ms" << std::endl; + std::cout << "Panda3D -> vpImage time: " << vpMath::getMean(fetchTime) << "ms +- " << vpMath::getStdev(fetchTime) << "ms" << std::endl; + std::cout << "Display time: " << vpMath::getMean(displayTime) << "ms +- " << vpMath::getStdev(displayTime) << "ms" << std::endl; } return 0; } From 1d8851763dc502f244b54fe970538c4909f675d9 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 23 Apr 2024 12:16:55 +0200 Subject: [PATCH 27/65] Add an interface to add lights to the scene --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 5 + modules/ar/include/visp3/ar/vpPanda3DLight.h | 102 ++++++++++++++++++ .../include/visp3/ar/vpPanda3DRGBRenderer.h | 7 +- .../include/visp3/ar/vpPanda3DRendererSet.h | 5 +- .../vpPanda3DBaseRenderer.cpp | 10 ++ .../vpPanda3DRGBRenderer.cpp | 6 ++ .../vpPanda3DRendererSet.cpp | 10 ++ tutorial/ar/tutorial-panda3d-renderer.cpp | 38 ++++--- 8 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DLight.h diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 01674660c5..f15b4d3e22 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -35,6 +35,7 @@ #include #if defined(VISP_HAVE_PANDA3D) +#include #include #include @@ -225,6 +226,10 @@ class VISP_EXPORT vpPanda3DBaseRenderer void setAbortOnPandaError(bool abort); void setForcedInvertTextures(bool invert); + static vpPoint vispPointToPanda(const vpPoint &point); + + void printStructure(); + protected: /** diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h new file mode 100644 index 0000000000..8be3d6ed42 --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -0,0 +1,102 @@ +#ifndef vpPand3DLight_h +#define vpPand3DLight_h + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include +#include +#include +#include +#include "nodePath.h" +#include "ambientLight.h" +#include "directionalLight.h" +#include "pointLight.h" + + + +class VISP_EXPORT vpPanda3DLight +{ +public: + vpPanda3DLight(const std::string &name, const vpRGBf &color) : m_name(name), m_color(color) { } + + const std::string &getName() const { return m_name; } + const vpRGBf &getColor() const { return m_color; } + virtual void addToScene(NodePath &scene) const = 0; + +protected: + std::string m_name; + vpRGBf m_color; +}; + +class VISP_EXPORT vpPanda3DAmbientLight : public vpPanda3DLight +{ +public: + vpPanda3DAmbientLight(const std::string &name, const vpRGBf &color) : vpPanda3DLight(name, color) { } + void addToScene(NodePath &scene) const vp_override + { + PT(AmbientLight) light = new AmbientLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + NodePath alnp = scene.attach_new_node(light); + scene.set_light(alnp); + } +}; + +class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight +{ +public: + vpPanda3DPointLight(const std::string &name, const vpRGBf &color, const vpColVector &position) : vpPanda3DLight(name, color), m_position(position) + { + if (position.size() != 3) { + throw vpException(vpException::dimensionError, "Point light position must be a 3 dimensional vector"); + } + } + void addToScene(NodePath &scene) const vp_override + { + PT(PointLight) light = new PointLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + NodePath np = scene.attach_new_node(light); + vpPoint posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); + np.set_pos(posPanda.get_X(), posPanda.get_Y(), posPanda.get_Z()); + scene.set_light(np); + } +private: + const vpPoint m_position; +}; + +class VISP_EXPORT vpPanda3DLightable +{ +public: + virtual ~vpPanda3DLightable() = default; + virtual void addLight(const vpPanda3DLight &light) = 0; +}; + +class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable +{ +public: + vpPanda3DLightableScene() : vpPanda3DLightable() + { } + + vpPanda3DLightableScene(NodePath &scene) : vpPanda3DLightable(), m_lightableScene(scene) + { } + + + + void addLight(const vpPanda3DLight &light) vp_override + { + if (m_lightableScene.is_empty()) { + throw vpException(vpException::notInitialized, "Tried to add a light to a scene that is not initialized."); + } + light.addToScene(m_lightableScene); + } +protected: + void setLightableScene(NodePath &scene) { m_lightableScene = scene; } +private: + NodePath m_lightableScene; +}; + + + +#endif +#endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index a2a5ae3c16..dae7ac1cce 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -37,11 +37,10 @@ #if defined(VISP_HAVE_PANDA3D) #include +#include #include -#include - -class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer +class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vpPanda3DLightableScene { public: vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB") { } @@ -56,7 +55,7 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer void getRender(vpImage &I) const; protected: - + void setupScene() vp_override; void setupRenderTarget() vp_override; private: diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index d27df7e26c..f12142a52d 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -38,8 +38,9 @@ #include #include +#include -class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer +class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vpPanda3DLightable { public: vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters); @@ -114,6 +115,8 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; + void addLight(const vpPanda3DLight& light) vp_override; + void addSubRenderer(std::shared_ptr renderer); template diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 87dcd48172..0bdc8dcdf5 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -202,7 +202,17 @@ void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) else { load_prc_file_data("", "copy-texture-inverted 0"); } +} + +vpPoint vpPanda3DBaseRenderer::vispPointToPanda(const vpPoint &point) +{ + return PANDA_T_VISP * point; +} +void vpPanda3DBaseRenderer::printStructure() +{ + m_renderRoot.ls(); } + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index d26cb5754a..a9761508c0 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -47,6 +47,12 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const // memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); } +void vpPanda3DRGBRenderer::setupScene() +{ + vpPanda3DBaseRenderer::setupScene(); + setLightableScene(m_renderRoot); +} + void vpPanda3DRGBRenderer::setupRenderTarget() { FrameBufferProperties fbp; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index a2b44dd883..2b3fb51d6a 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -117,6 +117,16 @@ void vpPanda3DRendererSet::setRenderParameters(const vpPanda3DRenderParameters & } } +void vpPanda3DRendererSet::addLight(const vpPanda3DLight &light) +{ + for (std::shared_ptr &renderer: m_subRenderers) { + vpPanda3DLightable *lightable = dynamic_cast(renderer.get()); + if (lightable != nullptr) { + lightable->addLight(light); + } + } +} + void vpPanda3DRendererSet::addSubRenderer(std::shared_ptr renderer) { for (std::shared_ptr &otherRenderer: m_subRenderers) { diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 9ac6ed03bf..2cccfe745d 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -100,7 +100,7 @@ int main(int argc, const char **argv) } std::cout << "Initializing framework" << std::endl; - renderer.initFramework(false); + renderer.initFramework(true); std::cout << "Loading object" << std::endl; NodePath object = renderer.loadObject(objectName, modelPath); @@ -109,16 +109,12 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); // rgbRenderer->getRenderRoot().set_shader_auto(100); - PT(AmbientLight) alight = new AmbientLight("ambient"); - alight->set_color(LColor(0.2, 0.2, 0.2, 1)); - NodePath alnp = rgbRenderer->getRenderRoot().attach_new_node(alight); - rgbRenderer->getRenderRoot().set_light(alnp); - PT(PointLight) plight = new PointLight("sun"); - plight->set_color(LColor(2.0, 2.0, 2.0, 1)); - NodePath plnp = rgbRenderer->getRenderRoot().attach_new_node(plight); - plnp.set_pos(0.4, -0.5, 0.1); - rgbRenderer->getRenderRoot().set_light(plnp); + vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); + renderer.addLight(alight); + vpPanda3DPointLight plight("Point", vpRGBf(50.0), vpColVector({ 0.4, -0.5, -0.5 })); + renderer.addLight(plight); + rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.5, 0.0, 0.0, 0.0)); @@ -148,8 +144,9 @@ int main(int argc, const char **argv) DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, renderParams.getImageHeight() + 80, "normals in camera space"); DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth() + 80, 0, "depth"); DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2 + 90, 0, "color"); - + renderer.renderFrame(); bool end = false; + bool firstFrame = true; std::vector renderTime, fetchTime, displayTime; while (!end) { float near = 0, far = 0; @@ -163,7 +160,6 @@ int main(int argc, const char **argv) renderer.renderFrame(); const double beforeFetch = vpTime::measureTimeMs(); - renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage); renderer.getRenderer()->getRender(colorImage); @@ -175,6 +171,7 @@ int main(int argc, const char **argv) displayDepth(depthImage, depthDisplayImage, near, far); vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); + if (stepByStep) { vpDisplay::displayText(colorImage, 50, 15, "Next frame: space", vpColor::red); } @@ -186,8 +183,8 @@ int main(int argc, const char **argv) renderTime.push_back(beforeFetch - beforeRender); fetchTime.push_back(beforeConvert - beforeFetch); displayTime.push_back(endDisplay - beforeConvert); + std::string s; if (stepByStep) { - std::string s; bool next = false; while (!next) { vpDisplay::getKeyboardEvent(colorImage, s, true); @@ -195,12 +192,21 @@ int main(int argc, const char **argv) next = true; } } - } + // if (firstFrame) { + // renderParams.setImageResolution(renderParams.getImageHeight() * 0.5, renderParams.getImageWidth() * 0.5); + // vpCameraParameters orig = renderParams.getCameraIntrinsics(); + // vpCameraParameters newCam(orig.get_px() * 0.5, orig.get_py() * 0.5, orig.get_u0() * 0.5, orig.get_v0() * 0.5); + // renderParams.setCameraIntrinsics(newCam); + // std::cout << renderParams.getImageHeight() << std::endl; + // //dDepth.setDownScalingFactor(0.5); + // renderer.setRenderParameters(renderParams); + // } + // firstFrame = false; const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; - vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); - vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0), 0.0 }), delta); + const vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); + const vpHomogeneousMatrix oToo = vpExponentialMap::direct(vpColVector({ 0.0, 0.0, 0.0, 0.0, vpMath::rad(20.0), 0.0 }), delta); renderer.setNodePose(objectName, wTo * oToo); } if (renderTime.size() > 0) { From 7bb702932256fdbe542bedadbf78ba34c6a5d981 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 23 Apr 2024 13:53:42 +0200 Subject: [PATCH 28/65] fix argument parsing for model name --- tutorial/ar/tutorial-panda3d-renderer.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 2cccfe745d..97331b9a35 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -53,7 +53,7 @@ int main(int argc, const char **argv) { bool invertTexture = false; bool stepByStep = false; - std::string modelPathCstr; + char *modelPathCstr; vpParseArgv::vpArgvInfo argTable[] = { {"-invert", vpParseArgv::ARGV_CONSTANT_BOOL, 0, (char *)&invertTexture, @@ -62,7 +62,6 @@ int main(int argc, const char **argv) "Path to the model to load."}, {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, "Show frames step by step."}, - {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, "Print the help."}, {(char *) nullptr, vpParseArgv::ARGV_END, (char *) nullptr, (char *) nullptr, (char *) nullptr} }; @@ -99,10 +98,10 @@ int main(int argc, const char **argv) renderer.setForcedInvertTextures(true); } - std::cout << "Initializing framework" << std::endl; + std::cout << "Initializing Panda3D rendering framework" << std::endl; renderer.initFramework(true); - std::cout << "Loading object" << std::endl; + std::cout << "Loading object " << modelPath << std::endl; NodePath object = renderer.loadObject(objectName, modelPath); std::cout << "Adding node to scene" <getRenderRoot().set_shader_auto(100); vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(50.0), vpColVector({ 0.4, -0.5, -0.5 })); + vpPanda3DPointLight plight("Point", vpRGBf(2.0), vpColVector({ 0.4, -0.5, -0.5 })); renderer.addLight(plight); rgbRenderer->printStructure(); From 4c7295c18540ce8c9d8f4608501d65166348f01e Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 23 Apr 2024 13:54:46 +0200 Subject: [PATCH 29/65] Update tutorial model --- tutorial/ar/data/deformed_sphere.bam | Bin 57657 -> 56235 bytes tutorial/ar/data/simple_cube.mtl | 12 ------- tutorial/ar/data/simple_cube.obj | 46 --------------------------- 3 files changed, 58 deletions(-) delete mode 100644 tutorial/ar/data/simple_cube.mtl delete mode 100644 tutorial/ar/data/simple_cube.obj diff --git a/tutorial/ar/data/deformed_sphere.bam b/tutorial/ar/data/deformed_sphere.bam index 4da7885825c789842bec09a9fb0e1ccd957e1ea5..877858f8b6a1dcd32b70fee6b7cde1146f110ec4 100644 GIT binary patch delta 2392 zcmZ{m`%fHI6vxk<8FpE^!xY#)Tx)rVv|A`{p;+32&=!}{c7ZM}ZF#gEV7kj|X%Hy! z0sd5DqA_@zru@+Ov$1LoKN$4~Uy&x7CMFs+(WrlezZ?DDyTcCS3^zNwchC9W`#E#( zIdkXJuhsWIQA=))Oi+;?Aff=(5u?vnNI-OO0C zmO=skHj9=9ZDp4bd ztxH-4X`K`u+>oZkjYX&)+Gkzb9ni+5>QH`!GKpY2t(ob5Yj)0B>@ep{){OPr*u4fZ zLn9v!HPm21#jv|b1EwC9GZ3P|I=+r=NSbrj?6^6_Yn79i?LkNVenDLTauX#Semtx+7@KIpr2ACc}ma!w|li`n>HE35HPiP#b{gKA>f} zff}_t{1XoUq{Bbu;qP_xJ8XtXA@Gp}!my5usV|5wn$54EM4n$M>>71C{L>D<%i*8# z@T=VXvtlANn~%&A|6Y^#e@=AKY<@K*^L&|U8g)DT9*5uS@XverHEwCNvsy_`?qW zs>8qL;n%zQ*KLNq{-`TqSi;3r)Arf-AE%KGeyyxCjYb{*n8UXm{taq*N3RZvXD4Q) zG(V_UsEw3f>(w6*pw=>L6`6$2ns|D?&p>iByN;nA=jFe|_}}wo>ZsM!WRjF(1VWU&c7)M{40;+*(pw5k&jqt0kzi z%-WZ4e5V8-hdkp&@`tNI^(=Uwt==zH&w)4aKv!9kXFf_8-9Ee?A71Yk zyz^V|Qs8~?5WGI{lHV|uUEn?I3FD#0qf4Af5PejSa*JEQrIB> z?&pLt$iM zo>d9Yi!4lCSsY_obE?~%wpb&L<3$6<;^@rG>{xwVNxY*>)TB@9hs+$1d7nb~P` esyHW^!qa%p%%-jETsUW$#w*cycFbA?m)L(#)I8n* delta 3763 zcmb`KYfKbZ6vxkw?Rd!bz|nFQFX7(7EfemGmE&2HX)b!5{V>t>>stp(!fyw5FvF|Cr`=aBMO?xA zAGrY{u6rmF>d{+Gz~>LKPLrMBP{XT~{JqtIHc9Yk2_Y7!p6)!U!Z0-rS%ZO2r!5gI z6b}!v&|l>RVhw9!kOX8FkYpe!KxP9;#a^N;JO_0V8sy++<3Sw=>hYWEkIEiBsEXOb z^h_%}TXxJ=JwW*7)h^h!ovL#OdFED4~M5;a}V- zZ+?Gm;M^N->U|^HglbHWKK>#OZ)&DGu?WlsLTF#;Kt)^}o&auToUBm3BDwKSyp_)k z#V*WUtLW2XCwSxFRBG?Es&bViIZ7 z%o3TPSD0SPou#(?H`l5s)dQ^!o|C!fFyd#1K+pd^YUb|Iz+F7(6z=*Tc@96C5axP9 zb)nUf3GRCKzj1BmnR(}dvwVcLRHyzgu6f{Us%egKpP7T}Z7K7GHRW6>{jmRM*Nv&EO*m7^-WRF-##ALPB|2}kq75<|L#0Ak8lDF^ zxCbo9wSyWSLK=p!g58qK>O`!CS~4G2qI8u(nvT-dOq4fMx&ZP~T40bqj?zLVTBA$D zwIljp*hR1jr6mUGVw9?>b5kXi zhH1x^Kq+d=4BBkemNOOZh*;+1A@txa<$>;i+#vfx()1GqhbxUX2Ot5;`?|Y5-d4SF z8Hi*hZS5inP$30+>LocO4Q%#?yrdWsbEY??EtKz3#>Ij#4hP-=FXGll#0q+fg&|HL zqRJbRWS_Ttn!f_J;s+5IaC_V(4TSn+ozI5Qf{(84%nMdK@Ra!nsDvu)Co9#k6WFZ5 zq%c$$hOKRwEVdf%fKElhOK>7kLlf3Se3G}(Jyke?Q=%3}ltskmg$3$#{w|%rTj$qD z^Ye}T2F~DooL$uLDIP?8innooA?(rkY_eIPQRnZ~`As^1Uo^kS$luQyoR2e>8X~O! z2aNo+(5&%`X^RECI=@Bdx9a@1X#P4QU*ZhT$7MhbgN%QWxAFdOfOd_~R)qyRbiPmL zck2AEX#OT6zuU;iwM7jnjNfDAyTGsU-L%C50i7Sz`G<7A9L+B^@_RXh_aE0QHH_dv z#1L=e{V#_;jn9^^1^RXVVVysq^N;9!Wzd>#+rm5&7*x(!Gp!yN1ahrW$vagr_Q3j5 zBd*G!v6C$x2idCJ3Di09?*;)j)#+`+%INJJr7f6Dwkrd{VdoCon$N;zt+#IHB~?;e zf%beK2Ah(9ZfVjGwhLckR8H^*|(tx~8&pB+k z=7z?qMqeRhkMh$=kF(K)x7UQ%WXjtY%{z_%XOOoadAX-vpCukZ-qcx#NSc+q?|PhG z6JCo6uhopFWZsR5o0-j=IF6GLmvJ6(2N5@Mm58KWxqi*#>@dOkOmLlMIA<5N8hL-% zS@*h;7d!c$?j}260W~;p)flQ4sNM4=lqV#8~WL8Nq8?3RuEm ziGfV8q|%w+Nrv!wBw()a8&QhB%V-$J7bJXMwgv)n+p0YJvM2PCqqsBlv*#!`jG#eX gFbS$6jPGqBufJXLhcM6do(R4o`S3+=0G$K*3wfAAO8@`> diff --git a/tutorial/ar/data/simple_cube.mtl b/tutorial/ar/data/simple_cube.mtl deleted file mode 100644 index 5ac7f9e997..0000000000 --- a/tutorial/ar/data/simple_cube.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender 3.3.1 MTL File: 'None' -# www.blender.org - -newmtl Material -Ns 111.687584 -Ka 0.948187 0.948187 0.948187 -Kd 0.800000 0.013142 0.026189 -Ks 0.691710 0.691710 0.691710 -Ke 0.000000 0.000000 0.000000 -Ni 1.450000 -d 1.000000 -illum 2 diff --git a/tutorial/ar/data/simple_cube.obj b/tutorial/ar/data/simple_cube.obj deleted file mode 100644 index cf6498cfdf..0000000000 --- a/tutorial/ar/data/simple_cube.obj +++ /dev/null @@ -1,46 +0,0 @@ -# Blender 3.3.1 -# www.blender.org -mtllib simple_cube.mtl -o Cube -v 1.000000 1.000000 1.000000 -v 1.000000 1.000000 -1.000000 -v 1.000000 -1.000000 1.000000 -v 1.000000 -1.000000 -1.000000 -v -1.000000 1.000000 1.000000 -v -1.000000 1.000000 -1.000000 -v -1.000000 -1.000000 1.000000 -v -1.000000 -1.000000 -1.000000 -vn -0.0000 -0.0000 1.0000 -vn -0.0000 -1.0000 -0.0000 -vn -1.0000 -0.0000 -0.0000 -vn -0.0000 -0.0000 -1.0000 -vn 1.0000 -0.0000 -0.0000 -vn -0.0000 1.0000 -0.0000 -vt 0.625000 0.500000 -vt 0.375000 0.500000 -vt 0.625000 0.750000 -vt 0.375000 0.750000 -vt 0.875000 0.500000 -vt 0.625000 0.250000 -vt 0.125000 0.500000 -vt 0.375000 0.250000 -vt 0.875000 0.750000 -vt 0.625000 1.000000 -vt 0.625000 0.000000 -vt 0.375000 0.000000 -vt 0.375000 1.000000 -vt 0.125000 0.750000 -s 0 -usemtl Material -f 5/5/1 3/3/1 1/1/1 -f 3/3/2 8/13/2 4/4/2 -f 7/11/3 6/8/3 8/12/3 -f 2/2/4 8/14/4 6/7/4 -f 1/1/5 4/4/5 2/2/5 -f 5/6/6 2/2/6 6/8/6 -f 5/5/1 7/9/1 3/3/1 -f 3/3/2 7/10/2 8/13/2 -f 7/11/3 5/6/3 6/8/3 -f 2/2/4 4/4/4 8/14/4 -f 1/1/5 3/3/5 4/4/5 -f 5/6/6 1/1/6 2/2/6 From 5f8113b15f1b7a7132dc55e8dcc13ad658c1e6e0 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 23 Apr 2024 17:24:19 +0200 Subject: [PATCH 30/65] Debugging color rendering with different formats --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 1 + modules/ar/include/visp3/ar/vpPanda3DLight.h | 103 +++++++++++++++++- .../vpPanda3DRGBRenderer.cpp | 1 + tutorial/ar/tutorial-panda3d-renderer.cpp | 10 +- 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index f15b4d3e22..6e8c7b8152 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -110,6 +110,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer if (resize) { for (GraphicsOutput *buffer: m_buffers) { + //buffer->get_type().is_derived_from() GraphicsBuffer *buf = dynamic_cast(buffer); if (buf == nullptr) { throw vpException(vpException::fatalError, "Panda3D: could not cast to GraphicsBuffer when rendering."); diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index 8be3d6ed42..daefaad1f8 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -1,3 +1,34 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #ifndef vpPand3DLight_h #define vpPand3DLight_h @@ -14,26 +45,65 @@ #include "directionalLight.h" #include "pointLight.h" - - +/** + * @brief Base class for a Light that can be added to a Panda3D scene. + * + * Note that modifying any object that inherits from this class + * after the method addToScene has been called * will not update the rendered light. + * + * \see https://docs.panda3d.org/1.10/cpp/programming/render-attributes/lighting + * + */ class VISP_EXPORT vpPanda3DLight { public: + /** + * @brief Build a new Panda3D light, given a unique name and an RGB color. + * + * + * @param name the name of the light: should be unique in the scene where the light will be added. + * @param color The color of the light: Each R,G,B component is unbounded and can exceed a value of 1 to increase its intensity. + */ vpPanda3DLight(const std::string &name, const vpRGBf &color) : m_name(name), m_color(color) { } + /** + * @brief Get the name of the light. + * + * This name should be unique and will be required when interacting with Panda3D to fetch the node. + */ const std::string &getName() const { return m_name; } + /** + * @brief Get the light's color + * + * @return const vpRGBf& + */ const vpRGBf &getColor() const { return m_color; } + + /** + * @brief Add the light to the scene. + * + * @param scene Scene where the light should be added. + */ virtual void addToScene(NodePath &scene) const = 0; protected: - std::string m_name; - vpRGBf m_color; + std::string m_name; //! Name of the light. Should be unique in the scene + vpRGBf m_color; //! RGB Color of the light. Can exceed 1 for each component. }; +/** + * @brief Class representing an ambient light. + * + * Ambient light are not physically possible, but are used to emulate light coming from all directions. + * They do not generate speculars or reflections. + * + * \see https://docs.panda3d.org/1.10/python/reference/panda3d.core.AmbientLight + */ class VISP_EXPORT vpPanda3DAmbientLight : public vpPanda3DLight { public: vpPanda3DAmbientLight(const std::string &name, const vpRGBf &color) : vpPanda3DLight(name, color) { } + void addToScene(NodePath &scene) const vp_override { PT(AmbientLight) light = new AmbientLight(m_name); @@ -43,15 +113,30 @@ class VISP_EXPORT vpPanda3DAmbientLight : public vpPanda3DLight } }; +/** + * @brief Class representing a Point Light. + * + * Point lights emit light all around them, from a single point. + */ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight { public: + /** + * @brief Build a new point light. + * + * \see vpPanda3DLight constructor. + * + * @param name name of the light + * @param color color of the light + * @param position Position in the scene of the light. Uses ViSP coordinates. + */ vpPanda3DPointLight(const std::string &name, const vpRGBf &color, const vpColVector &position) : vpPanda3DLight(name, color), m_position(position) { if (position.size() != 3) { throw vpException(vpException::dimensionError, "Point light position must be a 3 dimensional vector"); } } + void addToScene(NodePath &scene) const vp_override { PT(PointLight) light = new PointLight(m_name); @@ -61,14 +146,24 @@ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight np.set_pos(posPanda.get_X(), posPanda.get_Y(), posPanda.get_Z()); scene.set_light(np); } + private: const vpPoint m_position; }; +/** + * @brief Interface for objects, scenes or other Panda3D related data that can be lit by a vpPanda3DLight. + * + */ class VISP_EXPORT vpPanda3DLightable { public: virtual ~vpPanda3DLightable() = default; + /** + * @brief Light this lightable object with a new light + * + * @param light + */ virtual void addLight(const vpPanda3DLight &light) = 0; }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index a9761508c0..4e958da282 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -51,6 +51,7 @@ void vpPanda3DRGBRenderer::setupScene() { vpPanda3DBaseRenderer::setupScene(); setLightableScene(m_renderRoot); + //m_renderRoot.set_shader_auto(); } void vpPanda3DRGBRenderer::setupRenderTarget() diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 97331b9a35..a75c9ba47c 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -18,11 +18,6 @@ #include #include -#include -#include -#include - - void displayNormals(const vpImage &normalsImage, vpImage &normalDisplayImage) { @@ -81,6 +76,7 @@ int main(int argc, const char **argv) vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 1.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); + const std::string objectName = "object"; std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS)); @@ -110,7 +106,7 @@ int main(int argc, const char **argv) // rgbRenderer->getRenderRoot().set_shader_auto(100); vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(2.0), vpColVector({ 0.4, -0.5, -0.5 })); + vpPanda3DPointLight plight("Point", vpRGBf(2.0), vpColVector({ 0.0, -0.3, -0.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); @@ -142,7 +138,7 @@ int main(int argc, const char **argv) DisplayCls dNormals(normalDisplayImage, 0, 0, "normals in world space"); DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, renderParams.getImageHeight() + 80, "normals in camera space"); DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth() + 80, 0, "depth"); - DisplayCls dColor(colorImage, renderParams.getImageWidth() * 2 + 90, 0, "color"); + DisplayCls dColor(colorImage, renderParams.getImageWidth() * 3 + 90, 0, "color"); renderer.renderFrame(); bool end = false; bool firstFrame = true; From c3edefe31e8f35723d07f8ed0bba6a3a6189fb7d Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 23 Apr 2024 21:58:59 +0200 Subject: [PATCH 31/65] Add path to detect Panda from .dmg installation --- cmake/FindMyPanda3D.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index e7f2ccc147..a939a87f80 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -44,6 +44,7 @@ set(PANDA3D_INCLUDE_SEARCH_PATHS ${Panda3D_DIR}/built/include /usr/include/panda3d /usr/local/include/panda3d + /Library/Developer/Panda3D/include ) set(PANDA3D_LIBRARIES_SEARCH_PATHS @@ -54,6 +55,7 @@ set(PANDA3D_LIBRARIES_SEARCH_PATHS /usr/lib/panda3d /usr/local/lib/panda3d /usr/lib/x86_64-linux-gnu/panda3d + /Library/Developer/Panda3D/lib ) set(PANDA3D_LIBS From 8a7b365c708fca18cdd1ea1544456bcfb65d2aed Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 23 Apr 2024 22:00:38 +0200 Subject: [PATCH 32/65] Mute warning with Panda 1.10.14 SDK on macOS --- modules/ar/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 7a5eb94cde..4e026793f4 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -191,7 +191,7 @@ if(USE_OGRE) endif() if(USE_PANDA3D) - set(PANDA3D_CXX_FLAGS -Wno-unused-parameter -Wno-unused-variable -Wno-extra) + set(PANDA3D_CXX_FLAGS -Wno-unused-parameter -Wno-unused-variable -Wno-extra -Wno-reorder-ctor) set(PANDA3D_MODULE_SOURCES vpPanda3DBaseRenderer.cpp vpPanda3DGeometryRenderer.cpp vpPanda3DRGBRenderer.cpp vpPanda3DRenderParameters.cpp From c84a1e65423f8bcbbc5c11e0d2f8f09c9b594582 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 23 Apr 2024 22:00:57 +0200 Subject: [PATCH 33/65] Fix build on macOS --- modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 0bdc8dcdf5..3b54fa1b34 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -130,7 +130,7 @@ vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(const std::string &name) { NodePath object = m_renderRoot.find(name); if (object.is_empty()) { - throw vpException(vpException::badValue, "Node %s was not found", name); + throw vpException(vpException::badValue, "Node %s was not found", name.c_str()); } return getNodePose(object); } @@ -151,7 +151,7 @@ void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &n } NodePath object = m_renderRoot.find(name); if (object.is_empty()) { - throw vpException(vpException::badValue, "Node %s was not found", name); + throw vpException(vpException::badValue, "Node %s was not found", name.c_str()); } LPoint3 minP, maxP; object.calc_tight_bounds(minP, maxP, m_cameraPath); From 83a672394e75e0bd01c9300d5c0b1829184834fb Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 23 Apr 2024 22:23:20 +0200 Subject: [PATCH 34/65] Fix segfault when -model option is not used (meaning default model is used) --- tutorial/ar/tutorial-panda3d-renderer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index a75c9ba47c..567aa84589 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -48,7 +48,7 @@ int main(int argc, const char **argv) { bool invertTexture = false; bool stepByStep = false; - char *modelPathCstr; + char *modelPathCstr = nullptr; vpParseArgv::vpArgvInfo argTable[] = { {"-invert", vpParseArgv::ARGV_CONSTANT_BOOL, 0, (char *)&invertTexture, @@ -69,8 +69,11 @@ int main(int argc, const char **argv) return (false); } - std::string modelPath(modelPathCstr); - if (modelPath.size() == 0) { + std::string modelPath; + if (modelPathCstr) { + modelPath = modelPathCstr; + } + else { modelPath = "data/deformed_sphere.bam"; } vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 1.0); From 0612dca718a5677b3d194ed13df2ea3248aa05c0 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 23 Apr 2024 23:50:11 +0200 Subject: [PATCH 35/65] debug light positioning, add failsafes on buffer setup --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 2 +- modules/ar/include/visp3/ar/vpPanda3DLight.h | 26 ++++++++++++++++--- .../vpPanda3DBaseRenderer.cpp | 7 +++-- .../vpPanda3DGeometryRenderer.cpp | 6 +++++ .../vpPanda3DRGBRenderer.cpp | 9 ++++++- tutorial/ar/tutorial-panda3d-renderer.cpp | 7 +++-- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 6e8c7b8152..bb31b7cd55 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -227,7 +227,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer void setAbortOnPandaError(bool abort); void setForcedInvertTextures(bool invert); - static vpPoint vispPointToPanda(const vpPoint &point); + static vpColVector vispPointToPanda(const vpColVector &point); void printStructure(); diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index daefaad1f8..729ae03fab 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -129,26 +129,44 @@ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight * @param name name of the light * @param color color of the light * @param position Position in the scene of the light. Uses ViSP coordinates. + * @param attenuation Attenuation components of the light as a function of distance. + * Should be a vector of size 3 where the first component is the constant intensity factor (no falloff), + * the second is a linear falloff coefficient, and the last one is the quadratic falloff component. + * To follow the inverse square law, set this value vector to [0, 0, 1] + * To have no falloff, set it to [1, 0, 0]. */ - vpPanda3DPointLight(const std::string &name, const vpRGBf &color, const vpColVector &position) : vpPanda3DLight(name, color), m_position(position) + vpPanda3DPointLight(const std::string &name, const vpRGBf &color, const vpColVector &position, const vpColVector &attenuation) + : vpPanda3DLight(name, color), m_attenuation(attenuation) { if (position.size() != 3) { throw vpException(vpException::dimensionError, "Point light position must be a 3 dimensional vector"); } + m_position.resize(4, false); + m_position.insert(0, position); + m_position[3] = 1.0; + if (attenuation.size() != 3) { + throw vpException(vpException::dimensionError, "Point light attenuation components must be a 3 dimensional vector"); + } } void addToScene(NodePath &scene) const vp_override { PT(PointLight) light = new PointLight(m_name); light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + light->set_attenuation(LVecBase3(m_attenuation[0], m_attenuation[1], m_attenuation[2])); NodePath np = scene.attach_new_node(light); - vpPoint posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); - np.set_pos(posPanda.get_X(), posPanda.get_Y(), posPanda.get_Z()); + vpColVector posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); + np.set_pos(posPanda[0], posPanda[1], posPanda[2]); + std::cout << "posPanda = " << posPanda << std::endl; + std::cout << np.get_pos() << std::endl; + std::cout << scene.get_mat() << std::endl; + std::cout << np.get_mat(scene) << std::endl; scene.set_light(np); } private: - const vpPoint m_position; + vpColVector m_position; //! Position of the light, in homogeneous coordinates + vpColVector m_attenuation; //! Attenuation components: [constant, linear, quadratic] }; /** diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 3b54fa1b34..5ec32fcf3b 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -204,9 +204,12 @@ void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) } } -vpPoint vpPanda3DBaseRenderer::vispPointToPanda(const vpPoint &point) +vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) { - return PANDA_T_VISP * point; + vpColVector pandaPos = PANDA_T_VISP * point; + std::cout <<"PANDA POS = " << pandaPos << std::endl; + pandaPos /= pandaPos[3]; + return pandaPos; } void vpPanda3DBaseRenderer::printStructure() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 254adece07..285989a92b 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -130,6 +130,9 @@ void vpPanda3DGeometryRenderer::setupScene() void vpPanda3DGeometryRenderer::setupRenderTarget() { + if (m_window == nullptr) { + throw vpException(vpException::fatalError, "Cannot setup render target when window is null"); + } FrameBufferProperties fbp; fbp.set_rgb_color(true); fbp.set_float_depth(false); @@ -160,6 +163,9 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() m_normalDepthBuffer->set_clear_color(LColor(0.f)); m_normalDepthBuffer->set_clear_color_active(true); DisplayRegion *region = m_normalDepthBuffer->make_display_region(); + if (region == nullptr) { + throw vpException(vpException::fatalError, "Could not create display region"); + } region->set_camera(m_cameraPath); region->set_clear_color(LColor(0.f)); } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 4e958da282..39a8555022 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -51,11 +51,14 @@ void vpPanda3DRGBRenderer::setupScene() { vpPanda3DBaseRenderer::setupScene(); setLightableScene(m_renderRoot); - //m_renderRoot.set_shader_auto(); + m_renderRoot.set_shader_auto(); } void vpPanda3DRGBRenderer::setupRenderTarget() { + if (m_window == nullptr) { + throw vpException(vpException::fatalError, "Cannot setup render target when window is null"); + } FrameBufferProperties fbp; fbp.set_rgb_color(true); fbp.set_float_depth(false); @@ -63,6 +66,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() fbp.set_depth_bits(16); fbp.set_rgba_bits(8, 8, 8, 8); + WindowProperties win_prop; win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); @@ -86,6 +90,9 @@ void vpPanda3DRGBRenderer::setupRenderTarget() m_colorBuffer->set_clear_color(LColor(0.f)); m_colorBuffer->set_clear_color_active(true); DisplayRegion *region = m_colorBuffer->make_display_region(); + if (region == nullptr) { + throw vpException(vpException::fatalError, "Could not create display region"); + } region->set_camera(m_cameraPath); region->set_clear_color(LColor(0.f)); } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 567aa84589..d347676765 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -76,7 +76,7 @@ int main(int argc, const char **argv) else { modelPath = "data/deformed_sphere.bam"; } - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 1.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -106,10 +106,9 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); - // rgbRenderer->getRenderRoot().set_shader_auto(100); vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(2.0), vpColVector({ 0.0, -0.3, -0.0 })); + vpPanda3DPointLight plight("Point", vpRGBf(200.0), vpColVector({ 0.0, 0.0, -5.0 }), vpColVector({ 1.0, 0.0, 0.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); @@ -150,7 +149,7 @@ int main(int argc, const char **argv) float near = 0, far = 0; const double beforeComputeBB = vpTime::measureTimeMs(); rgbRenderer->computeNearAndFarPlanesFromNode(objectName, near, far); - renderParams.setClippingDistance(near, far); + // renderParams.setClippingDistance(near, far); renderer.setRenderParameters(renderParams); std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; From 00896209fd4690db1ccc4126475b9bd0c0c56263 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 00:39:11 +0200 Subject: [PATCH 36/65] Add another check on texture size when converting to vpImage --- .../panda3d-simulator/vpPanda3DBaseRenderer.cpp | 2 ++ .../vpPanda3DGeometryRenderer.cpp | 15 +++++++++++++-- .../panda3d-simulator/vpPanda3DRendererSet.cpp | 9 +++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 5ec32fcf3b..a65de360cf 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -34,6 +34,7 @@ #if defined(VISP_HAVE_PANDA3D) #include "load_prc_file.h" + const vpHomogeneousMatrix vpPanda3DBaseRenderer::VISP_T_PANDA({ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1., 0.0, @@ -44,6 +45,7 @@ const vpHomogeneousMatrix vpPanda3DBaseRenderer::PANDA_T_VISP(vpPanda3DBaseRende void vpPanda3DBaseRenderer::initFramework(bool showWindow) { + load_prc_file_data("", "load-display p3tinydisplay"); if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 285989a92b..ec367e4053 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -148,17 +148,23 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() GraphicsOutput *windowOutput = m_window->get_graphics_output(); GraphicsEngine *engine = windowOutput->get_engine(); GraphicsPipe *pipe = windowOutput->get_pipe(); - m_normalDepthBuffer = engine->make_output(pipe, "My Buffer", -100, fbp, win_prop, flags, + + m_normalDepthBuffer = engine->make_output(pipe, renderTypeToName(m_renderType), 0, fbp, win_prop, flags, windowOutput->get_gsg(), windowOutput); if (m_normalDepthBuffer == nullptr) { throw vpException(vpException::fatalError, "Could not create geometry info buffer"); } + // if (!m_normalDepthBuffer->is_valid()) { + // throw vpException(vpException::fatalError, "Geometry info buffer is invalid"); + // } m_buffers.push_back(m_normalDepthBuffer); m_normalDepthTexture = new Texture(); m_normalDepthBuffer->set_inverted(windowOutput->get_gsg()->get_copy_texture_inverted()); - fbp.setup_color_texture(m_normalDepthTexture); + // fbp.setup_color_texture(m_normalDepthTexture); + // m_normalDepthTexture->set_format(Texture::F_rgba32); + m_normalDepthBuffer->add_render_texture(m_normalDepthTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); m_normalDepthBuffer->set_clear_color(LColor(0.f)); m_normalDepthBuffer->set_clear_color_active(true); @@ -174,6 +180,11 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_y_size(), m_normalDepthTexture->get_x_size()); depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + unsigned expectedSize = m_normalDepthTexture->get_y_size() * m_normalDepthTexture->get_x_size() * sizeof(float) * 4; + unsigned actualSize = m_normalDepthTexture->get_ram_image_size(); + if (expectedSize != actualSize) { + throw vpException(vpException::fatalError, "Expected %d bytes, but got %d bytes", expectedSize, actualSize); + } float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); //#pragma omp parallel for simd diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 2b3fb51d6a..e9d1fdc333 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -33,6 +33,8 @@ #if defined(VISP_HAVE_PANDA3D) +#include "load_prc_file.h" + vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") { m_renderParameters = renderParameters; @@ -43,6 +45,12 @@ vpPanda3DRendererSet::~vpPanda3DRendererSet() void vpPanda3DRendererSet::initFramework(bool showWindow) { + + // load_prc_file_data("", "load-display p3tinydisplay"); + // load_prc_file_data("", "color-bits 32 32 32"); + // load_prc_file_data("", "depth-bits 16"); + + if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); } @@ -55,6 +63,7 @@ void vpPanda3DRendererSet::initFramework(bool showWindow) if (m_window == nullptr) { throw vpException(vpException::fatalError, "Could not open Panda3D window (hidden or visible)"); } + m_window->set_background_type(WindowFramework::BackgroundType::BT_black); for (std::shared_ptr &renderer: m_subRenderers) { renderer->initFromParent(m_framework, m_window); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index d347676765..1da45b90db 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -27,6 +27,7 @@ void displayNormals(const vpImage &normalsImage, normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); normalDisplayImage.bitmap[i].B = static_cast((normalsImage.bitmap[i].B + 1.0) * 127.5f); } + vpDisplay::display(normalDisplayImage); vpDisplay::flush(normalDisplayImage); } From d61af18538819ed90fdc60693041ccbf76d7c62f Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 12:31:35 +0200 Subject: [PATCH 37/65] Add texture information debug print, Try and reparse uint texture data when using tinydisplay (not working...) --- modules/ar/include/visp3/ar/vpPanda3DLight.h | 4 - .../vpPanda3DBaseRenderer.cpp | 2 - .../vpPanda3DGeometryRenderer.cpp | 75 +++++++++++++------ tutorial/ar/tutorial-panda3d-renderer.cpp | 4 +- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index 729ae03fab..2362549acc 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -157,10 +157,6 @@ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight NodePath np = scene.attach_new_node(light); vpColVector posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); np.set_pos(posPanda[0], posPanda[1], posPanda[2]); - std::cout << "posPanda = " << posPanda << std::endl; - std::cout << np.get_pos() << std::endl; - std::cout << scene.get_mat() << std::endl; - std::cout << np.get_mat(scene) << std::endl; scene.set_light(np); } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index a65de360cf..9799c6590f 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -45,7 +45,6 @@ const vpHomogeneousMatrix vpPanda3DBaseRenderer::PANDA_T_VISP(vpPanda3DBaseRende void vpPanda3DBaseRenderer::initFramework(bool showWindow) { - load_prc_file_data("", "load-display p3tinydisplay"); if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); @@ -209,7 +208,6 @@ void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) { vpColVector pandaPos = PANDA_T_VISP * point; - std::cout <<"PANDA POS = " << pandaPos << std::endl; pandaPos /= pandaPos[3]; return pandaPos; } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index ec367e4053..2c66339c87 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -142,16 +142,14 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() WindowProperties win_prop; win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); - // Don't open a window - force it to be an offscreen buffer. int flags = GraphicsPipe::BF_refuse_window | GraphicsPipe::BF_resizeable; GraphicsOutput *windowOutput = m_window->get_graphics_output(); GraphicsEngine *engine = windowOutput->get_engine(); GraphicsPipe *pipe = windowOutput->get_pipe(); - m_normalDepthBuffer = engine->make_output(pipe, renderTypeToName(m_renderType), 0, fbp, win_prop, flags, - windowOutput->get_gsg(), - windowOutput); + m_normalDepthBuffer = engine->make_output(pipe, renderTypeToName(m_renderType), -100, fbp, win_prop, flags, + windowOutput->get_gsg(), windowOutput); if (m_normalDepthBuffer == nullptr) { throw vpException(vpException::fatalError, "Could not create geometry info buffer"); @@ -162,9 +160,8 @@ void vpPanda3DGeometryRenderer::setupRenderTarget() m_buffers.push_back(m_normalDepthBuffer); m_normalDepthTexture = new Texture(); m_normalDepthBuffer->set_inverted(windowOutput->get_gsg()->get_copy_texture_inverted()); - // fbp.setup_color_texture(m_normalDepthTexture); - // m_normalDepthTexture->set_format(Texture::F_rgba32); - + fbp.setup_color_texture(m_normalDepthTexture); + m_normalDepthTexture->set_format(Texture::F_rgba32); m_normalDepthBuffer->add_render_texture(m_normalDepthTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); m_normalDepthBuffer->set_clear_color(LColor(0.f)); m_normalDepthBuffer->set_clear_color_active(true); @@ -180,32 +177,62 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_y_size(), m_normalDepthTexture->get_x_size()); depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); - unsigned expectedSize = m_normalDepthTexture->get_y_size() * m_normalDepthTexture->get_x_size() * sizeof(float) * 4; - unsigned actualSize = m_normalDepthTexture->get_ram_image_size(); - if (expectedSize != actualSize) { - throw vpException(vpException::fatalError, "Expected %d bytes, but got %d bytes", expectedSize, actualSize); - } - float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + // unsigned expectedSize = m_normalDepthTexture->get_y_size() * m_normalDepthTexture->get_x_size() * sizeof(float) * 4; + // unsigned actualSize = m_normalDepthTexture->get_ram_image_size(); + // if (expectedSize != actualSize) { + // throw vpException(vpException::fatalError, "Expected %d bytes, but got %d bytes", expectedSize, actualSize); + // } + std::stringstream ss; + ss << "Texture info:" << std::endl; + ss << "Has ram image: " << m_normalDepthTexture->has_ram_image() << std::endl; + ss << "Size (H x W): " << m_normalDepthTexture->get_y_size() << " x " << m_normalDepthTexture->get_x_size() << std::endl; + ss << "Number of channels: " << m_normalDepthTexture->get_num_components() << std::endl; + ss << "Bytes per channel: " << m_normalDepthTexture->get_component_width() << std::endl; + ss << "Channel type: " << m_normalDepthTexture->get_component_type() << std::endl; + std::cout << ss.str() << std::endl; + + if (m_normalDepthTexture->get_component_type() == Texture::T_float) { + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + for (unsigned int i = 0; i < normals.getSize(); ++i) { + normals.bitmap[i].B = (data[i * 4]); + normals.bitmap[i].G = (data[i * 4 + 1]); + normals.bitmap[i].R = (data[i * 4 + 2]); + depth.bitmap[i] = (data[i * 4 + 3]); + } -//#pragma omp parallel for simd - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (data[i * 4]); - normals.bitmap[i].G = (data[i * 4 + 1]); - normals.bitmap[i].R = (data[i * 4 + 2]); - depth.bitmap[i] = (data[i * 4 + 3]); } + else if (m_normalDepthTexture->get_component_type() == Texture::T_unsigned_byte) { + unsigned char *data = (unsigned char *)(&(m_normalDepthTexture->get_ram_image().front())); + for (unsigned int i = 0; i < normals.getSize(); ++i) { + normals.bitmap[i].B = (static_cast(data[i * 4]) / 127.5f - 1.0); + normals.bitmap[i].G = (static_cast(data[i * 4 + 1]) / 127.5f - 1.0); + normals.bitmap[i].R = (static_cast(data[i * 4 + 2]) / 127.5f - 1.0); + depth.bitmap[i] = ((static_cast(data[i * 4 + 3]))); + } + } +//#pragma omp parallel for simd } void vpPanda3DGeometryRenderer::getRender(vpImage &normals) const { normals.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); - float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); //#pragma omp parallel for simd - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (data[i * 4]); - normals.bitmap[i].G = (data[i * 4 + 1]); - normals.bitmap[i].R = (data[i * 4 + 2]); + if (m_normalDepthTexture->get_component_type() == Texture::T_float) { + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + for (unsigned int i = 0; i < normals.getSize(); ++i) { + normals.bitmap[i].B = (data[i * 4]); + normals.bitmap[i].G = (data[i * 4 + 1]); + normals.bitmap[i].R = (data[i * 4 + 2]); + } + } + else if (m_normalDepthTexture->get_component_type() == Texture::T_unsigned_byte) { + unsigned char *data = (unsigned char *)(&(m_normalDepthTexture->get_ram_image().front())); + for (unsigned int i = 0; i < normals.getSize(); ++i) { + normals.bitmap[i].B = (static_cast(data[i * 4]) / 127.5f - 1.0); + normals.bitmap[i].G = (static_cast(data[i * 4 + 1]) / 127.5f - 1.0); + normals.bitmap[i].R = (static_cast(data[i * 4 + 2]) / 127.5f - 1.0); + } } } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 1da45b90db..54b11d9649 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -99,7 +99,7 @@ int main(int argc, const char **argv) } std::cout << "Initializing Panda3D rendering framework" << std::endl; - renderer.initFramework(true); + renderer.initFramework(false); std::cout << "Loading object " << modelPath << std::endl; NodePath object = renderer.loadObject(objectName, modelPath); @@ -152,7 +152,7 @@ int main(int argc, const char **argv) rgbRenderer->computeNearAndFarPlanesFromNode(objectName, near, far); // renderParams.setClippingDistance(near, far); renderer.setRenderParameters(renderParams); - std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; + //std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); From 1953020ebdd92cf2f01d939e88061f4fae365458 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 12:42:17 +0200 Subject: [PATCH 38/65] Add debug logging on panda/gl side --- modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h | 1 + .../src/panda3d-simulator/vpPanda3DBaseRenderer.cpp | 12 ++++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 1 + 3 files changed, 14 insertions(+) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index bb31b7cd55..46a7ff3889 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -226,6 +226,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ void setAbortOnPandaError(bool abort); void setForcedInvertTextures(bool invert); + void enableDebugLog(); static vpColVector vispPointToPanda(const vpColVector &point); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 9799c6590f..7758d6d8f3 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -205,6 +205,15 @@ void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) } } +void vpPanda3DBaseRenderer::enableDebugLog() +{ + load_prc_file_data("", "gl-debug 1"); + load_prc_file_data("", "notify-level-display spam"); + +} + + + vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) { vpColVector pandaPos = PANDA_T_VISP * point; @@ -212,6 +221,9 @@ vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) return pandaPos; } + + + void vpPanda3DBaseRenderer::printStructure() { m_renderRoot.ls(); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 54b11d9649..dd84a7408d 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -94,6 +94,7 @@ int main(int argc, const char **argv) renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); + renderer.enableDebugLog(); if (invertTexture) { renderer.setForcedInvertTextures(true); } From 186cf907cc2268a70529afaa2235ece30c28c5db Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 15:19:13 +0200 Subject: [PATCH 39/65] revert shaders to version 120 --- .../panda3d-simulator/vpPanda3DGeometryRenderer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 2c66339c87..3ecf5e769e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -36,10 +36,10 @@ #include const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( -#version 140 +#version 120 -in vec3 p3d_Normal; -in vec3 p3d_Vertex; +attribute vec3 p3d_Normal; +attribute vec3 p3d_Vertex; varying vec3 oNormal; uniform mat3 p3d_NormalMatrix; @@ -62,9 +62,9 @@ void main() const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_WORLD = R"shader( -#version 140 -in vec3 p3d_Normal; -in vec3 p3d_Vertex; +#version 120 +attribute vec3 p3d_Normal; +attribute vec3 p3d_Vertex; uniform mat4 p3d_ModelViewMatrix; varying vec3 oNormal; varying float distToCamera; From e5fb416628868f3ca97217a7e7961477447dfa15 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 16:42:14 +0200 Subject: [PATCH 40/65] Debugging on macos x86, some issues with p3d attribs --- modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp | 2 ++ .../ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp | 4 ++-- tutorial/ar/tutorial-panda3d-renderer.cpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 7758d6d8f3..fa9388d3f4 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -34,6 +34,7 @@ #if defined(VISP_HAVE_PANDA3D) #include "load_prc_file.h" +#include const vpHomogeneousMatrix vpPanda3DBaseRenderer::VISP_T_PANDA({ 1.0, 0.0, 0.0, 0.0, @@ -79,6 +80,7 @@ void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr frame void vpPanda3DBaseRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); + m_renderRoot.set_antialias(AntialiasAttrib::M_none); } void vpPanda3DBaseRenderer::setupCamera() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 3ecf5e769e..c48d43172f 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -82,12 +82,11 @@ void main() )shader"; const char *vpPanda3DGeometryRenderer::SHADER_FRAG_NORMAL_AND_DEPTH = R"shader( -#version 140 +#version 120 varying vec3 oNormal; varying float distToCamera; - void main() { vec3 n = normalize(oNormal); @@ -203,6 +202,7 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_component_type() == Texture::T_unsigned_byte) { unsigned char *data = (unsigned char *)(&(m_normalDepthTexture->get_ram_image().front())); + std::cout << "AAAAAAA" << std::endl; for (unsigned int i = 0; i < normals.getSize(); ++i) { normals.bitmap[i].B = (static_cast(data[i * 4]) / 127.5f - 1.0); normals.bitmap[i].G = (static_cast(data[i * 4 + 1]) / 127.5f - 1.0); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index dd84a7408d..d922bfd05e 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -100,7 +100,7 @@ int main(int argc, const char **argv) } std::cout << "Initializing Panda3D rendering framework" << std::endl; - renderer.initFramework(false); + renderer.initFramework(true); std::cout << "Loading object " << modelPath << std::endl; NodePath object = renderer.loadObject(objectName, modelPath); From 6afe3ec84af3e6c25ed4cec88a679f48a13d072f Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Wed, 24 Apr 2024 17:36:21 +0200 Subject: [PATCH 41/65] switch shaders to 330 --- .../vpPanda3DBaseRenderer.cpp | 2 +- .../vpPanda3DGeometryRenderer.cpp | 47 ++++++++++++------- .../vpPanda3DRendererSet.cpp | 2 +- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index fa9388d3f4..b2a751e4e0 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -80,7 +80,7 @@ void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr frame void vpPanda3DBaseRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); - m_renderRoot.set_antialias(AntialiasAttrib::M_none); + //m_renderRoot.set_antialias(AntialiasAttrib::M_none); } void vpPanda3DBaseRenderer::setupCamera() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index c48d43172f..856d91072d 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -36,63 +36,74 @@ #include const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( -#version 120 +#version 330 -attribute vec3 p3d_Normal; -attribute vec3 p3d_Vertex; +in vec3 p3d_Normal; +in vec4 p3d_Vertex; -varying vec3 oNormal; +out vec3 oNormal; uniform mat3 p3d_NormalMatrix; uniform mat4 p3d_ModelViewMatrix; +uniform mat4 p3d_ModelViewProjectionMatrix; -varying float distToCamera; +out float distToCamera; void main() { - gl_Position = ftransform(); + //gl_Position = ftransform(); + + gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; // View space is Z-up right handed, flip z and y oNormal = p3d_NormalMatrix * normalize(p3d_Normal); oNormal.yz = oNormal.zy; oNormal.y = -oNormal.y; - vec4 cs_position = p3d_ModelViewMatrix * vec4(p3d_Vertex, 1.0); + vec4 cs_position = p3d_ModelViewMatrix * p3d_Vertex; distToCamera = -cs_position.z; } )shader"; const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_WORLD = R"shader( -#version 120 -attribute vec3 p3d_Normal; -attribute vec3 p3d_Vertex; +#version 330 +in vec3 p3d_Normal; +in vec4 p3d_Vertex; uniform mat4 p3d_ModelViewMatrix; -varying vec3 oNormal; -varying float distToCamera; +uniform mat4 p3d_ModelViewProjectionMatrix; +out vec3 oNormal; +out float distToCamera; void main() { - gl_Position = ftransform(); + //gl_Position = ftransform(); + gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; oNormal = normalize(p3d_Normal); oNormal.yz = oNormal.zy; oNormal.y = -oNormal.y; - vec4 cs_position = p3d_ModelViewMatrix * vec4(p3d_Vertex, 1.0); + vec4 cs_position = p3d_ModelViewMatrix * p3d_Vertex; distToCamera = -cs_position.z; } )shader"; const char *vpPanda3DGeometryRenderer::SHADER_FRAG_NORMAL_AND_DEPTH = R"shader( -#version 120 +#version 330 + +in vec3 oNormal; +in float distToCamera; +out vec4 p3d_FragColor; +out vec4 fragColor; + +out vec4 p3d_FragData; -varying vec3 oNormal; -varying float distToCamera; void main() { vec3 n = normalize(oNormal); //if (!gl_FrontFacing) //n = -n; - gl_FragColor = vec4(n, distToCamera); + p3d_FragData = vec4(n, distToCamera); + } )shader"; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index e9d1fdc333..07d2f78efa 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -48,7 +48,7 @@ void vpPanda3DRendererSet::initFramework(bool showWindow) // load_prc_file_data("", "load-display p3tinydisplay"); // load_prc_file_data("", "color-bits 32 32 32"); - // load_prc_file_data("", "depth-bits 16"); + load_prc_file_data("", "gl-version 3 2"); if (m_framework.use_count() > 0) { From 0caeea83c9abb88f8408345d742d0cb88e89463e Mon Sep 17 00:00:00 2001 From: FELTON Samuel Date: Wed, 24 Apr 2024 23:56:02 +0200 Subject: [PATCH 42/65] Enable build on windows --- cmake/FindMyPanda3D.cmake | 10 +++++++--- .../visp3/ar/vpPanda3DRenderParameters.h | 14 ++++++------- .../vpPanda3DBaseRenderer.cpp | 8 +++++--- tutorial/ar/tutorial-panda3d-renderer.cpp | 20 +++++++++---------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index a939a87f80..99a4b99aee 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -50,14 +50,19 @@ set(PANDA3D_INCLUDE_SEARCH_PATHS set(PANDA3D_LIBRARIES_SEARCH_PATHS $ENV{Panda3D_DIR}/lib $ENV{Panda3D_DIR}/built/lib + $ENV{Panda3D_DIR}/bin ${Panda3D_DIR}/lib ${Panda3D_DIR}/built/lib + ${Panda3D_DIR}/bin /usr/lib/panda3d /usr/local/lib/panda3d /usr/lib/x86_64-linux-gnu/panda3d /Library/Developer/Panda3D/lib ) + +message(${Panda3D_DIR}) + set(PANDA3D_LIBS panda p3framework pandaexpress p3dtoolconfig p3dtool p3direct @@ -70,8 +75,7 @@ set(PANDA3D_LIBS set(Panda3D_LIBRARIES "") set(ALL_LIBS_FOUND TRUE) foreach(lib_name ${PANDA3D_LIBS}) - find_library(Panda3D_${lib_name}_LIBRARY ${lib_name} PATHS ${PANDA3D_LIBRARIES_SEARCH_PATHS}) - + find_library(Panda3D_${lib_name}_LIBRARY NAMES "lib${lib_name}" ${lib_name} PATHS ${PANDA3D_LIBRARIES_SEARCH_PATHS}) if(NOT Panda3D_${lib_name}_LIBRARY) set(ALL_LIBS_FOUND FALSE) else() @@ -82,7 +86,7 @@ foreach(lib_name ${PANDA3D_LIBS}) endforeach() find_path(Panda3D_INCLUDE_DIRS panda.h PATHS ${PANDA3D_INCLUDE_SEARCH_PATHS}) - +message(${Panda3D_INCLUDE_DIRS}) include(FindPackageHandleStandardArgs) # Handle the QUIETLY and REQUIRED arguments and set the Panda3D_FOUND to TRUE # if all listed variables are TRUE diff --git a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h index 870f50f245..20b3705568 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h @@ -80,16 +80,16 @@ class VISP_EXPORT vpPanda3DRenderParameters /** * @brief Set the clipping distance. When a panda camera uses these render parameters, objects that are closer than "near" or further than "far" will be clipped. * - * @param near near clipping distance - * @param far far clipping distance + * @param nearV near clipping distance + * @param farV far clipping distance */ - void setClippingDistance(double near, double far) + void setClippingDistance(double nearV, double farV) { - if (far < near) { - std::swap(near, far); + if (farV < nearV) { + std::swap(nearV, farV); } - m_clipNear = near; - m_clipFar = far; + m_clipNear = nearV; + m_clipFar = farV; } unsigned int getImageWidth() const { return m_width; } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index b2a751e4e0..95226d3b24 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -33,6 +33,8 @@ #if defined(VISP_HAVE_PANDA3D) +#include + #include "load_prc_file.h" #include @@ -147,7 +149,7 @@ vpHomogeneousMatrix vpPanda3DBaseRenderer::getNodePose(NodePath &object) return vpHomogeneousMatrix(t, q) * PANDA_T_VISP; } -void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &name, float &near, float &far) +void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &name, float &nearV, float &farV) { if (m_camera == nullptr) { throw vpException(vpException::notInitialized, "Cannot compute planes when the camera is not initialized"); @@ -158,8 +160,8 @@ void vpPanda3DBaseRenderer::computeNearAndFarPlanesFromNode(const std::string &n } LPoint3 minP, maxP; object.calc_tight_bounds(minP, maxP, m_cameraPath); - near = std::max(0.f, minP.get_y()); - far = std::max(near, maxP.get_y()); + nearV = vpMath::maximum(0.f, minP.get_y()); + farV = vpMath::maximum(nearV, maxP.get_y()); } NodePath vpPanda3DBaseRenderer::loadObject(const std::string &nodeName, const std::string &modelPath) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index d922bfd05e..777ecf2a42 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -21,8 +21,8 @@ void displayNormals(const vpImage &normalsImage, vpImage &normalDisplayImage) { -#pragma omp parallel for simd - for (unsigned int i = 0; i < normalsImage.getSize(); ++i) { +#pragma omp parallel for + for (int i = 0; i < normalsImage.getSize(); ++i) { normalDisplayImage.bitmap[i].R = static_cast((normalsImage.bitmap[i].R + 1.0) * 127.5f); normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); normalDisplayImage.bitmap[i].B = static_cast((normalsImage.bitmap[i].B + 1.0) * 127.5f); @@ -32,11 +32,11 @@ void displayNormals(const vpImage &normalsImage, vpDisplay::flush(normalDisplayImage); } void displayDepth(const vpImage &depthImage, - vpImage &depthDisplayImage, float near, float far) + vpImage &depthDisplayImage, float nearV, float farV) { -#pragma omp parallel for simd - for (unsigned int i = 0; i < depthImage.getSize(); ++i) { - float val = std::max(0.f, (depthImage.bitmap[i] - near) / (far - near)); +#pragma omp parallel for + for (int i = 0; i < depthImage.getSize(); ++i) { + float val = std::max(0.f, (depthImage.bitmap[i] - nearV) / (farV - nearV)); depthDisplayImage.bitmap[i] = static_cast(val * 255.f); } vpDisplay::display(depthDisplayImage); @@ -148,10 +148,10 @@ int main(int argc, const char **argv) bool firstFrame = true; std::vector renderTime, fetchTime, displayTime; while (!end) { - float near = 0, far = 0; + float nearV = 0, farV = 0; const double beforeComputeBB = vpTime::measureTimeMs(); - rgbRenderer->computeNearAndFarPlanesFromNode(objectName, near, far); - // renderParams.setClippingDistance(near, far); + rgbRenderer->computeNearAndFarPlanesFromNode(objectName, nearV, farV); + renderParams.setClippingDistance(nearV, farV); renderer.setRenderParameters(renderParams); //std::cout << "Update clipping plane took " << vpTime::measureTimeMs() - beforeComputeBB << std::endl; @@ -167,7 +167,7 @@ int main(int argc, const char **argv) displayNormals(normalsImage, normalDisplayImage); displayNormals(cameraNormalsImage, cameraNormalDisplayImage); - displayDepth(depthImage, depthDisplayImage, near, far); + displayDepth(depthImage, depthDisplayImage, nearV, farV); vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); From 34bf478596f50fa10e66aad7bf590642f3aedcbb Mon Sep 17 00:00:00 2001 From: FELTON Samuel Date: Thu, 25 Apr 2024 11:09:00 +0200 Subject: [PATCH 43/65] Improve usability --- modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h | 2 +- modules/ar/include/visp3/ar/vpPanda3DRendererSet.h | 5 ++--- .../ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp | 9 +++++++-- .../ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp | 2 +- .../ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp | 8 ++++++-- tutorial/ar/tutorial-panda3d-renderer.cpp | 10 ++++++++-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 46a7ff3889..e45d1e8390 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -70,7 +70,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer * * @param showWindow whether the created window should be visible */ - virtual void initFramework(bool showWindow); + virtual void initFramework(); /** * @brief diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index f12142a52d..48dc8fc4c6 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -49,9 +49,8 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp /** * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers * - * @param showWindow Whether a window should be shown */ - void initFramework(bool showWindow) vp_override; + void initFramework() vp_override; /** * @brief Set the pose of the camera, using the ViSP convention. This change is propagated to all subrenderers @@ -115,7 +114,7 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; - void addLight(const vpPanda3DLight& light) vp_override; + void addLight(const vpPanda3DLight &light) vp_override; void addSubRenderer(std::shared_ptr renderer); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 95226d3b24..3d48ff4021 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -46,7 +46,7 @@ const vpHomogeneousMatrix vpPanda3DBaseRenderer::VISP_T_PANDA({ }); const vpHomogeneousMatrix vpPanda3DBaseRenderer::PANDA_T_VISP(vpPanda3DBaseRenderer::VISP_T_PANDA.inverse()); -void vpPanda3DBaseRenderer::initFramework(bool showWindow) +void vpPanda3DBaseRenderer::initFramework() { if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, @@ -56,8 +56,13 @@ void vpPanda3DBaseRenderer::initFramework(bool showWindow) m_framework->open_framework(); WindowProperties winProps; winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); - int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + int flags = GraphicsPipe::BF_refuse_window; m_window = m_framework->open_window(winProps, flags); + // try and reopen with visible window + if (m_window == nullptr) { + winProps.set_minimized(true); + m_window = m_framework->open_window(winProps, 0); + } if (m_window == nullptr) { throw vpException(vpException::notInitialized, "Panda3D renderer: Could not create the requested window when performing initialization."); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 39a8555022..3de6354340 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -51,7 +51,7 @@ void vpPanda3DRGBRenderer::setupScene() { vpPanda3DBaseRenderer::setupScene(); setLightableScene(m_renderRoot); - m_renderRoot.set_shader_auto(); + //m_renderRoot.set_shader_auto(); } void vpPanda3DRGBRenderer::setupRenderTarget() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 07d2f78efa..991031dd4e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -43,7 +43,7 @@ vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &rend vpPanda3DRendererSet::~vpPanda3DRendererSet() { } -void vpPanda3DRendererSet::initFramework(bool showWindow) +void vpPanda3DRendererSet::initFramework() { // load_prc_file_data("", "load-display p3tinydisplay"); @@ -58,8 +58,12 @@ void vpPanda3DRendererSet::initFramework(bool showWindow) m_framework->open_framework(); WindowProperties winProps; winProps.set_size(LVecBase2i(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight())); - int flags = showWindow ? 0 : GraphicsPipe::BF_refuse_window; + int flags = GraphicsPipe::BF_refuse_window; m_window = m_framework->open_window(winProps, flags); + if (m_window == nullptr) { + winProps.set_minimized(true); + m_window = m_framework->open_window(winProps, 0); + } if (m_window == nullptr) { throw vpException(vpException::fatalError, "Could not open Panda3D window (hidden or visible)"); } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 777ecf2a42..a1c32da20b 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -49,6 +49,7 @@ int main(int argc, const char **argv) { bool invertTexture = false; bool stepByStep = false; + bool debug = false; char *modelPathCstr = nullptr; vpParseArgv::vpArgvInfo argTable[] = { @@ -58,6 +59,8 @@ int main(int argc, const char **argv) "Path to the model to load."}, {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, "Show frames step by step."}, + {"-debug", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&debug, + "Show Opengl/Panda3D debug message."}, {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, "Print the help."}, {(char *) nullptr, vpParseArgv::ARGV_END, (char *) nullptr, (char *) nullptr, (char *) nullptr} }; @@ -94,13 +97,16 @@ int main(int argc, const char **argv) renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); - renderer.enableDebugLog(); + if (debug) { + renderer.enableDebugLog(); + + } if (invertTexture) { renderer.setForcedInvertTextures(true); } std::cout << "Initializing Panda3D rendering framework" << std::endl; - renderer.initFramework(true); + renderer.initFramework(); std::cout << "Loading object " << modelPath << std::endl; NodePath object = renderer.loadObject(objectName, modelPath); From 36713cab55e0246b5a37973e2fbfd69e78ba9444 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Thu, 25 Apr 2024 11:35:21 +0200 Subject: [PATCH 44/65] Remove cmake debug message that brings an issue on macOS --- cmake/FindMyPanda3D.cmake | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmake/FindMyPanda3D.cmake b/cmake/FindMyPanda3D.cmake index 99a4b99aee..22bbde66c8 100644 --- a/cmake/FindMyPanda3D.cmake +++ b/cmake/FindMyPanda3D.cmake @@ -60,9 +60,6 @@ set(PANDA3D_LIBRARIES_SEARCH_PATHS /Library/Developer/Panda3D/lib ) - -message(${Panda3D_DIR}) - set(PANDA3D_LIBS panda p3framework pandaexpress p3dtoolconfig p3dtool p3direct From d16a3229fb7d54135b16b9f9e9945aac54ff0c60 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 26 Apr 2024 18:09:22 +0200 Subject: [PATCH 45/65] Start work on RGB shader --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 3 + .../vpPanda3DBaseRenderer.cpp | 2 +- .../vpPanda3DGeometryRenderer.cpp | 16 ++--- .../vpPanda3DRGBRenderer.cpp | 64 +++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index dae7ac1cce..dc60a68098 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -61,6 +61,9 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp private: Texture *m_colorTexture; GraphicsOutput *m_colorBuffer; + static const char* COOK_TORRANCE_VERT; + static const char* COOK_TORRANCE_FRAG; + }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 3d48ff4021..154b0fbc47 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -71,7 +71,7 @@ void vpPanda3DBaseRenderer::initFramework() setupScene(); setupCamera(); setupRenderTarget(); - m_window->get_display_region_3d()->set_camera(m_cameraPath); + //m_window->get_display_region_3d()->set_camera(m_cameraPath); } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 856d91072d..dfd77db411 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -192,14 +192,14 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImagehas_ram_image() << std::endl; - ss << "Size (H x W): " << m_normalDepthTexture->get_y_size() << " x " << m_normalDepthTexture->get_x_size() << std::endl; - ss << "Number of channels: " << m_normalDepthTexture->get_num_components() << std::endl; - ss << "Bytes per channel: " << m_normalDepthTexture->get_component_width() << std::endl; - ss << "Channel type: " << m_normalDepthTexture->get_component_type() << std::endl; - std::cout << ss.str() << std::endl; + // std::stringstream ss; + // ss << "Texture info:" << std::endl; + // ss << "Has ram image: " << m_normalDepthTexture->has_ram_image() << std::endl; + // ss << "Size (H x W): " << m_normalDepthTexture->get_y_size() << " x " << m_normalDepthTexture->get_x_size() << std::endl; + // ss << "Number of channels: " << m_normalDepthTexture->get_num_components() << std::endl; + // ss << "Bytes per channel: " << m_normalDepthTexture->get_component_width() << std::endl; + // ss << "Channel type: " << m_normalDepthTexture->get_component_type() << std::endl; + // std::cout << ss.str() << std::endl; if (m_normalDepthTexture->get_component_type() == Texture::T_float) { float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 3de6354340..dfbbb93d3e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -33,6 +33,65 @@ #if defined(VISP_HAVE_PANDA3D) +const char *vpPanda3DRGBRenderer::COOK_TORRANCE_VERT = R"shader( +#version 330 + +in vec3 p3d_Normal; +in vec4 p3d_Vertex; + +out vec3 oNormal; +uniform mat3 p3d_NormalMatrix; +uniform mat4 p3d_ModelViewMatrix; +uniform mat4 p3d_ModelViewProjectionMatrix; + +void main() +{ + gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; + // View space is Z-up right handed, flip z and y + oNormal = p3d_NormalMatrix * normalize(p3d_Normal); + oNormal.yz = oNormal.zy; + oNormal.y = -oNormal.y; + vec4 cs_position = p3d_ModelViewMatrix * p3d_Vertex; +} +)shader"; + +const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( +#version 330 + +in vec3 oNormal; + +out vec4 p3d_FragData; + +uniform struct { + vec4 ambient; +} p3d_LightModel; + +uniform struct p3d_LightSourceParameters { + // Primary light color. + vec4 color; + + // View-space position. If w=0, this is a directional light, with the xyz + // being -direction. + vec4 position; + + // constant, linear, quadratic attenuation in one vector + vec3 attenuation; + +} p3d_LightSource[4]; + +void main() +{ + vec3 n = normalize(oNormal); + p3d_FragData = p3d_LightModel.ambient * vec4(1.f, 0.f, 0.f,0.f); + for(int i = 0; i < p3d_LightSource.length(); ++i) { + vec3 aFac = p3d_LightSource[i].attenuation; + float attenuation = 1.f / (aFac[0] + aFac[1] * dist, aFac[2] * dist * dist); + //p3d_FragData += p3d_LightSource[i].color; + } +} +)shader"; + + void vpPanda3DRGBRenderer::getRender(vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); @@ -51,6 +110,11 @@ void vpPanda3DRGBRenderer::setupScene() { vpPanda3DBaseRenderer::setupScene(); setLightableScene(m_renderRoot); + PT(Shader) shader; + shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + COOK_TORRANCE_VERT, + COOK_TORRANCE_FRAG); + m_renderRoot.set_shader(shader); //m_renderRoot.set_shader_auto(); } From 69a8f78e453360a5f7d460ead98b1b8aa7443103 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Sat, 27 Apr 2024 20:42:23 +0200 Subject: [PATCH 46/65] cook torrance seems to be working --- .../vpPanda3DRGBRenderer.cpp | 90 +++++++++++++++++-- tutorial/ar/tutorial-panda3d-renderer.cpp | 2 +- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index dfbbb93d3e..16e27b00f8 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -40,25 +40,32 @@ in vec3 p3d_Normal; in vec4 p3d_Vertex; out vec3 oNormal; +out vec4 viewVertex; + uniform mat3 p3d_NormalMatrix; uniform mat4 p3d_ModelViewMatrix; uniform mat4 p3d_ModelViewProjectionMatrix; +in vec2 p3d_MultiTexCoord0; +out vec2 texcoords; + + void main() { gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; - // View space is Z-up right handed, flip z and y oNormal = p3d_NormalMatrix * normalize(p3d_Normal); - oNormal.yz = oNormal.zy; - oNormal.y = -oNormal.y; - vec4 cs_position = p3d_ModelViewMatrix * p3d_Vertex; + viewVertex = p3d_ModelViewMatrix * p3d_Vertex; + texcoords = p3d_MultiTexCoord0; } )shader"; const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( #version 330 +#define M_PI 3.1415926535897932384626433832795 + in vec3 oNormal; +in vec4 viewVertex; out vec4 p3d_FragData; @@ -79,14 +86,81 @@ uniform struct p3d_LightSourceParameters { } p3d_LightSource[4]; +uniform struct p3d_MaterialParameters { + vec4 ambient; + vec4 diffuse; + vec4 emission; + vec3 specular; + float shininess; + + // These properties are new in 1.10. + vec4 baseColor; + float roughness; + float metallic; + float refractiveIndex; +} p3d_Material; + +in vec2 texcoords; +uniform sampler2D p3d_Texture0; + + +float D(float roughness2, float hn) +{ + return (1.f / (M_PI * roughness2)) * pow(hn, (2.f / roughness2) - 2.f); +} + +float G(float hn, float nv, float nl, float vh) +{ + return min(1.0, min((2.f * hn * nv) / vh, (2.f * hn * nl) / vh)); +} + +float computeF0(float ior) +{ + return pow(ior - 1.0, 2) / pow(ior + 1.0, 2.0); +} +float F(float F0, float vh) +{ + return F0 + (1.f - F0) * pow(1.f - vh, 5); +} + + void main() { - vec3 n = normalize(oNormal); - p3d_FragData = p3d_LightModel.ambient * vec4(1.f, 0.f, 0.f,0.f); + vec3 n = normalize(oNormal); // normalized normal vector + vec3 v = normalize(-viewVertex.xyz); // normalized view vector + float nv = max(0.f, dot(n, v)); + float roughness2 = pow(p3d_Material.roughness, 2); + float F0 = computeF0(p3d_Material.refractiveIndex); + + p3d_FragData = p3d_LightModel.ambient * p3d_Material.ambient; + + for(int i = 0; i < p3d_LightSource.length(); ++i) { + + vec3 lf = p3d_LightSource[i].position.xyz - (viewVertex.xyz * p3d_LightSource[i].position.w); + float lightDist = length(lf); + vec3 l = normalize(lf); // normalized light vector + vec3 h = normalize(l + v); // half way vector + float hn = dot(h, n); + float nl = max(0.f, dot(n, l)); + float vh = max(0.f, dot(v, h)); + vec3 aFac = p3d_LightSource[i].attenuation; - float attenuation = 1.f / (aFac[0] + aFac[1] * dist, aFac[2] * dist * dist); - //p3d_FragData += p3d_LightSource[i].color; + float attenuation = 1.f / (aFac[0] + aFac[1] * lightDist + aFac[2] * lightDist * lightDist); + + float DV = D(roughness2, hn); + float GV = G(hn, nv, nl, vh); + float FV = F(F0, vh); + + float rs = (DV * GV * FV) / (4.f * nl * nv); + + float shininess = p3d_Material.shininess; + p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (p3d_Material.diffuse + shininess * rs * vec4(p3d_Material.specular, 1.f)); + + + //p3d_FragData.r = attenuation; + //p3d_FragData = diffuse; + } } )shader"; diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index a1c32da20b..905114468a 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -116,7 +116,7 @@ int main(int argc, const char **argv) vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(200.0), vpColVector({ 0.0, 0.0, -5.0 }), vpColVector({ 1.0, 0.0, 0.0 })); + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.2, -0.4 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); From 99916e011002ebaa3874a75030f38ffb64a92c22 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 29 Apr 2024 18:28:57 +0200 Subject: [PATCH 47/65] more work on cook torrance model, weighting diffuse and specular terms --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 6 +- .../vpPanda3DRGBRenderer.cpp | 77 +++++++++++++++---- tutorial/ar/tutorial-panda3d-renderer.cpp | 4 +- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index dc60a68098..e77b6c27c6 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -54,6 +54,8 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp */ void getRender(vpImage &I) const; + void addNodeToScene(const NodePath &object) vp_override; + protected: void setupScene() vp_override; void setupRenderTarget() vp_override; @@ -61,8 +63,8 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp private: Texture *m_colorTexture; GraphicsOutput *m_colorBuffer; - static const char* COOK_TORRANCE_VERT; - static const char* COOK_TORRANCE_FRAG; + static const char *COOK_TORRANCE_VERT; + static const char *COOK_TORRANCE_FRAG; }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 16e27b00f8..eeab32af1d 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -48,7 +48,28 @@ uniform mat4 p3d_ModelViewProjectionMatrix; in vec2 p3d_MultiTexCoord0; out vec2 texcoords; +out vec3 F0; +uniform struct p3d_MaterialParameters { + vec4 ambient; + vec4 diffuse; + vec4 emission; + vec3 specular; + float shininess; + + // These properties are new in 1.10. + vec4 baseColor; + float roughness; + float metallic; + float refractiveIndex; +} p3d_Material; + +vec3 computeF0(float ior, float metallic, vec3 baseColor) +{ + float F0f = pow(abs((1.0 - ior) / (1.0 + ior)), 2.0); + vec3 F0 = vec3(F0f, F0f, F0f); + return mix(F0, baseColor, metallic); +} void main() { @@ -56,16 +77,22 @@ void main() oNormal = p3d_NormalMatrix * normalize(p3d_Normal); viewVertex = p3d_ModelViewMatrix * p3d_Vertex; texcoords = p3d_MultiTexCoord0; + F0 = computeF0(p3d_Material.refractiveIndex, p3d_Material.metallic, p3d_Material.baseColor.xyz); } )shader"; const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( #version 330 +//#define HAS_TEXTURE 1 +//#define SPECULAR 1 + #define M_PI 3.1415926535897932384626433832795 + in vec3 oNormal; in vec4 viewVertex; +in vec3 F0; out vec4 p3d_FragData; @@ -101,8 +128,9 @@ uniform struct p3d_MaterialParameters { } p3d_Material; in vec2 texcoords; +#ifdef HAS_TEXTURE uniform sampler2D p3d_Texture0; - +#endif float D(float roughness2, float hn) { @@ -114,11 +142,9 @@ float G(float hn, float nv, float nl, float vh) return min(1.0, min((2.f * hn * nv) / vh, (2.f * hn * nl) / vh)); } -float computeF0(float ior) -{ - return pow(ior - 1.0, 2) / pow(ior + 1.0, 2.0); -} -float F(float F0, float vh) + + +vec3 F(vec3 F0, float vh) { return F0 + (1.f - F0) * pow(1.f - vh, 5); } @@ -130,8 +156,6 @@ void main() vec3 v = normalize(-viewVertex.xyz); // normalized view vector float nv = max(0.f, dot(n, v)); float roughness2 = pow(p3d_Material.roughness, 2); - float F0 = computeF0(p3d_Material.refractiveIndex); - p3d_FragData = p3d_LightModel.ambient * p3d_Material.ambient; @@ -148,23 +172,41 @@ void main() vec3 aFac = p3d_LightSource[i].attenuation; float attenuation = 1.f / (aFac[0] + aFac[1] * lightDist + aFac[2] * lightDist * lightDist); - float DV = D(roughness2, hn); - float GV = G(hn, nv, nl, vh); - float FV = F(F0, vh); - - float rs = (DV * GV * FV) / (4.f * nl * nv); + vec3 FV = clamp(F(F0, vh), 0.0, 1.0); + vec3 kd = (1.f - FV) * (1.f - p3d_Material.metallic); + #ifdef HAS_TEXTURE + vec4 diffuseColor = texture(p3d_Texture0, texcoords); + #else + vec4 diffuseColor = p3d_Material.baseColor; + #endif - float shininess = p3d_Material.shininess; - p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (p3d_Material.diffuse + shininess * rs * vec4(p3d_Material.specular, 1.f)); + //diffuseColor.xyz = (1.f - FV) * (1.f - p3d_Material.metallic) * diffuseColor.xyz; - //p3d_FragData.r = attenuation; - //p3d_FragData = diffuse; + #ifdef SPECULAR + float DV = D(roughness2, hn); + float GV = G(hn, nv, nl, vh); + vec3 rs = (DV * GV * FV) / (4.f * nl * nv); + vec3 specularColor = rs * p3d_Material.specular; + #else + vec3 specularColor = vec3(0.0, 0.0, 0.0); + #endif + //p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (diffuseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); + p3d_FragData = diffuseColor; } } )shader"; +void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) +{ + NodePath objectInScene = object.copy_to(m_renderRoot); + objectInScene.set_name(object.get_name()); + objectInScene.ls(); + std::cout << objectInScene.has_texture() << std::endl; + setNodePose(objectInScene, vpHomogeneousMatrix()); +} + void vpPanda3DRGBRenderer::getRender(vpImage &I) const { @@ -203,6 +245,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() fbp.set_float_color(false); fbp.set_depth_bits(16); fbp.set_rgba_bits(8, 8, 8, 8); + fbp.set_srgb_color(true); WindowProperties win_prop; diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 905114468a..86a0621c2f 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -114,14 +114,14 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); - vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); + vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.5)); renderer.addLight(alight); vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.2, -0.4 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; - renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.5, 0.0, 0.0, 0.0)); + renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); vpImage normalsImage; vpImage cameraNormalsImage; From 1f558367a93aeb41f0a9dcbf22939526f54d3eef Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 29 Apr 2024 20:15:46 +0200 Subject: [PATCH 48/65] Fix cook torrance model, Add light contrib map example in tutorial --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 6 +- .../vpPanda3DRGBRenderer.cpp | 56 ++++++++++------- tutorial/ar/tutorial-panda3d-renderer.cpp | 62 ++++++++++++++----- 3 files changed, 85 insertions(+), 39 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index e77b6c27c6..e4d9d7a983 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -43,7 +43,9 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vpPanda3DLightableScene { public: - vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB") { } + vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB"), m_showSpeculars(true) { } + vpPanda3DRGBRenderer(bool showSpeculars) : vpPanda3DBaseRenderer(showSpeculars ? "RGB" : "RGB-diffuse"), m_showSpeculars(showSpeculars) { } + /** * @brief Store the render resulting from calling renderFrame() into a vpImage. @@ -59,8 +61,10 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp protected: void setupScene() vp_override; void setupRenderTarget() vp_override; + virtual std::string makeFragmentShader(bool hasTexture, bool specular); private: + bool m_showSpeculars; Texture *m_colorTexture; GraphicsOutput *m_colorBuffer; static const char *COOK_TORRANCE_VERT; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index eeab32af1d..775e8612a4 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -82,11 +82,7 @@ void main() )shader"; const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( -#version 330 - -//#define HAS_TEXTURE 1 -//#define SPECULAR 1 - +// Version 330, specified when generating shader #define M_PI 3.1415926535897932384626433832795 @@ -146,7 +142,7 @@ float G(float hn, float nv, float nl, float vh) vec3 F(vec3 F0, float vh) { - return F0 + (1.f - F0) * pow(1.f - vh, 5); + return F0 + (vec3(1.f, 1.f, 1.f) - F0) * pow(1.f - vh, 5); } @@ -172,8 +168,8 @@ void main() vec3 aFac = p3d_LightSource[i].attenuation; float attenuation = 1.f / (aFac[0] + aFac[1] * lightDist + aFac[2] * lightDist * lightDist); - vec3 FV = clamp(F(F0, vh), 0.0, 1.0); - vec3 kd = (1.f - FV) * (1.f - p3d_Material.metallic); + vec3 FV = F(F0, vh); + vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); #ifdef HAS_TEXTURE vec4 diffuseColor = texture(p3d_Texture0, texcoords); #else @@ -181,29 +177,49 @@ void main() #endif - //diffuseColor.xyz = (1.f - FV) * (1.f - p3d_Material.metallic) * diffuseColor.xyz; #ifdef SPECULAR - float DV = D(roughness2, hn); - float GV = G(hn, nv, nl, vh); - vec3 rs = (DV * GV * FV) / (4.f * nl * nv); - vec3 specularColor = rs * p3d_Material.specular; + vec3 specularColor = vec3(0.f, 0.f, 0.f); + if(nl > 0.f && nv > 0.f) { + float DV = D(roughness2, hn); + float GV = G(hn, nv, nl, vh); + vec3 rs = (DV * GV * FV) / (4.f * nl * nv); + specularColor = rs * p3d_Material.baseColor.rgb; + } #else vec3 specularColor = vec3(0.0, 0.0, 0.0); #endif - //p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (diffuseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); - p3d_FragData = diffuseColor; + p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (diffuseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); } } )shader"; +std::string vpPanda3DRGBRenderer::makeFragmentShader(bool hasTexture, bool specular) +{ + std::stringstream ss; + ss << "#version 330" << std::endl; + if (hasTexture) { + ss << "#define HAS_TEXTURE 1" << std::endl; + } + if (specular) { + ss << "#define SPECULAR 1" << std::endl; + } + ss << vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG; + std::cout << ss.str() << std::endl; + return ss.str(); +} + void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); - objectInScene.ls(); - std::cout << objectInScene.has_texture() << std::endl; + PT(Shader) shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + COOK_TORRANCE_VERT, + makeFragmentShader(false, m_showSpeculars)); + + objectInScene.set_shader(shader); + setNodePose(objectInScene, vpHomogeneousMatrix()); } @@ -226,12 +242,6 @@ void vpPanda3DRGBRenderer::setupScene() { vpPanda3DBaseRenderer::setupScene(); setLightableScene(m_renderRoot); - PT(Shader) shader; - shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, - COOK_TORRANCE_VERT, - COOK_TORRANCE_FRAG); - m_renderRoot.set_shader(shader); - //m_renderRoot.set_shader_auto(); } void vpPanda3DRGBRenderer::setupRenderTarget() diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 86a0621c2f..fc779132ca 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -44,12 +44,25 @@ void displayDepth(const vpImage &depthImage, } +void displayLightDifference(const vpImage &colorImage, const vpImage &colorDiffuseOnly, vpImage &lightDifference) +{ +#pragma omp parallel for + for (int i = 0; i < colorImage.getSize(); ++i) { + float I1 = 0.299 * colorImage.bitmap[i].R + 0.587 * colorImage.bitmap[i].G * +0.114 * colorImage.bitmap[i].B; + float I2 = 0.299 * colorDiffuseOnly.bitmap[i].R + 0.587 * colorDiffuseOnly.bitmap[i].G * +0.114 * colorDiffuseOnly.bitmap[i].B; + lightDifference.bitmap[i] = static_cast(abs(I1 - I2)); + } + vpDisplay::display(lightDifference); + vpDisplay::flush(lightDifference); +} + int main(int argc, const char **argv) { bool invertTexture = false; bool stepByStep = false; bool debug = false; + bool showLightContrib = false; char *modelPathCstr = nullptr; vpParseArgv::vpArgvInfo argTable[] = { @@ -59,6 +72,8 @@ int main(int argc, const char **argv) "Path to the model to load."}, {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, "Show frames step by step."}, + {"-specular", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&showLightContrib, + "Show frames step by step."}, {"-debug", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&debug, "Show Opengl/Panda3D debug message."}, {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, @@ -80,7 +95,7 @@ int main(int argc, const char **argv) else { modelPath = "data/deformed_sphere.bam"; } - vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(600, 600, 320, 240), 480, 640, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -90,16 +105,20 @@ int main(int argc, const char **argv) std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); + std::shared_ptr rgbDiffuseRenderer = std::shared_ptr(new vpPanda3DRGBRenderer(false)); renderer.addSubRenderer(geometryRenderer); renderer.addSubRenderer(cameraRenderer); renderer.addSubRenderer(rgbRenderer); + if (showLightContrib) { + renderer.addSubRenderer(rgbDiffuseRenderer); + } + renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); if (debug) { renderer.enableDebugLog(); - } if (invertTexture) { renderer.setForcedInvertTextures(true); @@ -114,7 +133,7 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); - vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.5)); + vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.2, -0.4 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); @@ -126,12 +145,14 @@ int main(int argc, const char **argv) vpImage normalsImage; vpImage cameraNormalsImage; vpImage depthImage; + unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); + vpImage colorImage(h, w); + vpImage colorDiffuseOnly(h, w); + vpImage lightDifference(h, w); - vpImage colorImage(renderParams.getImageHeight(), renderParams.getImageWidth()); - vpImage normalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); - vpImage cameraNormalDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); - - vpImage depthDisplayImage(renderParams.getImageHeight(), renderParams.getImageWidth()); + vpImage normalDisplayImage(h, w); + vpImage cameraNormalDisplayImage(h, w); + vpImage depthDisplayImage(h, w); #if defined(VISP_HAVE_GTK) using DisplayCls = vpDisplayGTK; @@ -146,9 +167,15 @@ int main(int argc, const char **argv) #endif DisplayCls dNormals(normalDisplayImage, 0, 0, "normals in world space"); - DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, renderParams.getImageHeight() + 80, "normals in camera space"); - DisplayCls dDepth(depthDisplayImage, renderParams.getImageWidth() + 80, 0, "depth"); - DisplayCls dColor(colorImage, renderParams.getImageWidth() * 3 + 90, 0, "color"); + DisplayCls dNormalsCamera(cameraNormalDisplayImage, 0, h + 80, "normals in camera space"); + DisplayCls dDepth(depthDisplayImage, w + 80, 0, "depth"); + DisplayCls dColor(colorImage, w + 80, h + 80, "color"); + + DisplayCls dImageDiff; + if (showLightContrib) { + dImageDiff.init(lightDifference, w * 2 + 90, 0, "Specular/reflectance contribution"); + } + renderer.renderFrame(); bool end = false; bool firstFrame = true; @@ -163,17 +190,22 @@ int main(int argc, const char **argv) const double beforeRender = vpTime::measureTimeMs(); renderer.renderFrame(); - const double beforeFetch = vpTime::measureTimeMs(); renderer.getRenderer(geometryRenderer->getName())->getRender(normalsImage, depthImage); renderer.getRenderer(cameraRenderer->getName())->getRender(cameraNormalsImage); - renderer.getRenderer()->getRender(colorImage); + renderer.getRenderer(rgbRenderer->getName())->getRender(colorImage); + if (showLightContrib) { + renderer.getRenderer(rgbDiffuseRenderer->getName())->getRender(colorDiffuseOnly); + } const double beforeConvert = vpTime::measureTimeMs(); displayNormals(normalsImage, normalDisplayImage); displayNormals(cameraNormalsImage, cameraNormalDisplayImage); displayDepth(depthImage, depthDisplayImage, nearV, farV); + if (showLightContrib) { + displayLightDifference(colorImage, colorDiffuseOnly, lightDifference); + } vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); @@ -199,11 +231,11 @@ int main(int argc, const char **argv) } } // if (firstFrame) { - // renderParams.setImageResolution(renderParams.getImageHeight() * 0.5, renderParams.getImageWidth() * 0.5); + // renderParams.setImageResolution(h * 0.5, w * 0.5); // vpCameraParameters orig = renderParams.getCameraIntrinsics(); // vpCameraParameters newCam(orig.get_px() * 0.5, orig.get_py() * 0.5, orig.get_u0() * 0.5, orig.get_v0() * 0.5); // renderParams.setCameraIntrinsics(newCam); - // std::cout << renderParams.getImageHeight() << std::endl; + // std::cout << h << std::endl; // //dDepth.setDownScalingFactor(0.5); // renderer.setRenderParameters(renderParams); // } From 0cb2f04eac10958d3a461472e79ef6be7a7c6078 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Mon, 29 Apr 2024 20:27:52 +0200 Subject: [PATCH 49/65] fix light contrib conversion --- tutorial/ar/tutorial-panda3d-renderer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index fc779132ca..c85a05be44 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -48,9 +48,9 @@ void displayLightDifference(const vpImage &colorImage, const vpImage(abs(I1 - I2)); + float I1 = 0.299 * colorImage.bitmap[i].R + 0.587 * colorImage.bitmap[i].G + 0.114 * colorImage.bitmap[i].B; + float I2 = 0.299 * colorDiffuseOnly.bitmap[i].R + 0.587 * colorDiffuseOnly.bitmap[i].G + 0.114 * colorDiffuseOnly.bitmap[i].B; + lightDifference.bitmap[i] = static_cast(round(abs(I1 - I2))); } vpDisplay::display(lightDifference); vpDisplay::flush(lightDifference); From ebfbdc8665b000fb64fc120e1c6979140ec26cd5 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 30 Apr 2024 01:01:08 +0200 Subject: [PATCH 50/65] Support for basic filters on output textures --- modules/ar/CMakeLists.txt | 2 +- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 25 +++- .../include/visp3/ar/vpPanda3DCommonFilters.h | 24 ++++ .../visp3/ar/vpPanda3DGeometryRenderer.h | 1 + .../visp3/ar/vpPanda3DPostProcessFilter.h | 47 ++++++++ .../include/visp3/ar/vpPanda3DRGBRenderer.h | 2 + .../include/visp3/ar/vpPanda3DRendererSet.h | 12 +- .../vpPanda3DCommonFilters.cpp | 56 +++++++++ .../vpPanda3DPostProcessFilter.cpp | 114 ++++++++++++++++++ .../vpPanda3DRGBRenderer.cpp | 2 +- .../vpPanda3DRendererSet.cpp | 32 ++++- tutorial/ar/tutorial-panda3d-renderer.cpp | 27 ++++- 12 files changed, 331 insertions(+), 13 deletions(-) create mode 100644 modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h create mode 100644 modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp create mode 100644 modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 4e026793f4..1844714daa 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -195,7 +195,7 @@ if(USE_PANDA3D) set(PANDA3D_MODULE_SOURCES vpPanda3DBaseRenderer.cpp vpPanda3DGeometryRenderer.cpp vpPanda3DRGBRenderer.cpp vpPanda3DRenderParameters.cpp - vpPanda3DRendererSet.cpp + vpPanda3DRendererSet.cpp vpPanda3DPostProcessFilter.cpp vpPanda3DCommonFilters.cpp ) foreach(panda_src_name ${PANDA3D_MODULE_SOURCES}) vp_set_source_file_compile_flag(src/panda3d-simulator/${panda_src_name} ${PANDA3D_CXX_FLAGS}) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index e45d1e8390..cf9594fa39 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -56,7 +56,7 @@ class VISP_EXPORT vpPanda3DBaseRenderer { public: vpPanda3DBaseRenderer(const std::string &rendererName) - : m_name(rendererName), m_framework(nullptr), m_window(nullptr), m_camera(nullptr) + : m_name(rendererName), m_renderOrder(-100), m_framework(nullptr), m_window(nullptr), m_camera(nullptr) { setVerticalSyncEnabled(false); } @@ -121,12 +121,28 @@ class VISP_EXPORT vpPanda3DBaseRenderer } } - // If renderer is already initialize, modify camera properties + // If renderer is already initialized, modify camera properties if (m_camera != nullptr) { m_renderParameters.setupPandaCamera(m_camera); } } + /** + * @brief Returns true if this renderer process 3D data and its scene root can be interacted with. + * + * This value could be false, if for instance it is redefined in a subclass that performs postprocessing on a texture. + */ + virtual bool isRendering3DScene() const { return true; } + + /** + * @brief Get the rendering order of this renderer. + * If a renderer A has a lower order value than B, it will be rendered before B. + * This is useful, if for instance, B is a postprocessing filter that depends on the result of B. + * + * @return int + */ + int getRenderOrder() const { return m_renderOrder; } + /** * @brief Set the camera's pose. * The pose is specified using the ViSP convention (Y-down right handed). @@ -232,6 +248,8 @@ class VISP_EXPORT vpPanda3DBaseRenderer void printStructure(); + virtual GraphicsOutput *getMainOutputBuffer() { return nullptr; } + protected: /** @@ -254,13 +272,14 @@ class VISP_EXPORT vpPanda3DBaseRenderer */ virtual void setupRenderTarget() { } + const static vpHomogeneousMatrix VISP_T_PANDA; //! Homogeneous transformation matrix to convert from the Panda coordinate system (right-handed Z-up) to the ViSP coordinate system (right-handed Y-Down) const static vpHomogeneousMatrix PANDA_T_VISP; //! Inverse of VISP_T_PANDA - protected: const std::string m_name; //! name of the renderer + int m_renderOrder; //! Rendering priority for this renderer and its buffers. A lower value will be rendered first. Should be used when calling make_output in setupRenderTarget() std::shared_ptr m_framework; //! Pointer to the active panda framework PT(WindowFramework) m_window; //! Pointer to owning window, which can create buffers etc. It is not necessarily visible. vpPanda3DRenderParameters m_renderParameters; //! Rendering parameters diff --git a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h new file mode 100644 index 0000000000..6fbf7abab1 --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h @@ -0,0 +1,24 @@ +#ifndef vpPanda3DCommonFilters_h +#define vpPanda3DCommonFilters_h + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include + +class vpPanda3DRGBRenderer; + +class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput); + FrameBufferProperties getBufferProperties() const vp_override; + void getRender(vpImage &I) const; + +private: + static const char *FRAGMENT_SHADER; + +}; +#endif +#endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 7f304c979a..ecf96705ff 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -81,6 +81,7 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer void getRender(vpImage &depth) const; + GraphicsOutput *getMainOutputBuffer() vp_override { return m_normalDepthBuffer; } protected: void setupScene() vp_override; diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h new file mode 100644 index 0000000000..963ac815dc --- /dev/null +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -0,0 +1,47 @@ +#ifndef vpPanda3DPostProcessFilter_h +#define vpPanda3DPostProcessFilter_h + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include +#include "cardMaker.h" +#include "orthographicLens.h" + + +class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer +{ +public: + vpPanda3DPostProcessFilter(const std::string &name, const std::shared_ptr &inputRenderer, bool isOutput, std::string fragmentShader) + : vpPanda3DBaseRenderer(name), m_inputRenderer(inputRenderer), m_isOutput(isOutput), m_fragmentShader(fragmentShader) + { + m_renderOrder = m_inputRenderer->getRenderOrder() + 1; + } + bool isRendering3DScene() const vp_override + { + return false; + } + +protected: + void setupScene() vp_override; + + void setupCamera() vp_override; + + void setupRenderTarget() vp_override; + + void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; + + virtual FrameBufferProperties getBufferProperties() const = 0; + + std::shared_ptr m_inputRenderer; + bool m_isOutput; //! Whether this filter is an output to be used and should be copied to ram + std::string m_fragmentShader; + PT(Shader) shader; + Texture *m_texture; + GraphicsOutput *m_buffer; + + static const char *FILTER_VERTEX_SHADER; +}; + +#endif +#endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index e4d9d7a983..39f6431ae2 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -58,6 +58,8 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp void addNodeToScene(const NodePath &object) vp_override; + GraphicsOutput *getMainOutputBuffer() vp_override { return m_colorBuffer; } + protected: void setupScene() vp_override; void setupRenderTarget() vp_override; diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index 48dc8fc4c6..6602d7b5b6 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -47,8 +47,10 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp virtual ~vpPanda3DRendererSet(); /** - * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers + * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers. * + * The subrenderers will be initialized in the order of their priority as defined by vpPanda3DBaseRenderer::getRenderOrder + * Thus, if a renderer B depends on A for its render, and if B.getRenderOrder() > A.getRenderOrder() it can rely on A being initialized when B.initFromParent is called (along with the setupCamera, setupRenderTarget). */ void initFramework() vp_override; @@ -110,12 +112,20 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp */ vpHomogeneousMatrix getNodePose(NodePath &object) vp_override; + /** + * \warn this method is not supported and will throw + */ void addNodeToScene(const NodePath &object) vp_override; void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; void addLight(const vpPanda3DLight &light) vp_override; + /** + * @brief Add a new subrenderer + * + * @param renderer + */ void addSubRenderer(std::shared_ptr renderer); template diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp new file mode 100644 index 0000000000..98062831e4 --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -0,0 +1,56 @@ +#include +#include + +#if defined(VISP_HAVE_PANDA3D) + +const char *vpPanda3DLuminanceFilter::FRAGMENT_SHADER = R"shader( +#version 330 + +in vec2 texcoords; + + +out vec4 p3d_FragData; +uniform sampler2D p3d_Texture0; + +void main() { + vec4 v = texture(p3d_Texture0, texcoords); + p3d_FragData.b = 0.299 * v.r + 0.587 * v.g + 0.114 * v.b; +} + +)shader"; + + +vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput) + : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, std::string(vpPanda3DLuminanceFilter::FRAGMENT_SHADER)) +{ + +} +FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const +{ + FrameBufferProperties fbp; + fbp.set_depth_bits(0); + fbp.set_rgba_bits(0, 0, 8, 0); + fbp.set_float_color(false); + return fbp; +} +void vpPanda3DLuminanceFilter::getRender(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image + I.resize(m_texture->get_y_size(), m_texture->get_x_size()); + unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); + if (indexMultiplier != 1) { + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i] = data[i * indexMultiplier]; + } + } + else { + memcpy(I.bitmap, &data[0], I.getSize() * sizeof(unsigned char)); + } +} + + + +#endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp new file mode 100644 index 0000000000..662f10011d --- /dev/null +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -0,0 +1,114 @@ +#include + + +#if defined(VISP_HAVE_PANDA3D) + +const char *vpPanda3DPostProcessFilter::FILTER_VERTEX_SHADER = R"shader( +#version 330 +in vec4 p3d_Vertex; +uniform mat4 p3d_ModelViewProjectionMatrix; +in vec2 p3d_MultiTexCoord0; +out vec2 texcoords; + +void main() +{ + gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; + texcoords = p3d_MultiTexCoord0; +} +)shader"; + + +void vpPanda3DPostProcessFilter::setupScene() +{ + CardMaker cm("cm"); + cm.set_frame_fullscreen_quad(); + m_renderRoot = NodePath(cm.generate()); // Render root is a 2D rectangle + m_renderRoot.set_depth_test(false); + m_renderRoot.set_depth_write(false); + GraphicsOutput *buffer = m_inputRenderer->getMainOutputBuffer(); + if (buffer == nullptr) { + throw vpException(vpException::fatalError, + "Cannot add a postprocess filter to a renderer that does not define getMainOutputBuffer()"); + } + shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + FILTER_VERTEX_SHADER, + m_fragmentShader); + m_renderRoot.set_shader(shader); + std::cout << m_fragmentShader << std::endl; + m_renderRoot.set_texture(buffer->get_texture()); + m_renderRoot.set_attrib(LightRampAttrib::make_identity()); +} + +void vpPanda3DPostProcessFilter::setupCamera() +{ + m_cameraPath = m_window->make_camera(); + m_camera = (Camera *)m_cameraPath.node(); + PT(OrthographicLens) lens = new OrthographicLens(); + lens->set_film_size(2, 2); + lens->set_film_offset(0, 0); + lens->set_near_far(-1000, 1000); + m_camera->set_lens(lens); + m_cameraPath = m_renderRoot.attach_new_node(m_camera); + m_camera->set_scene(m_renderRoot); +} + +void vpPanda3DPostProcessFilter::setupRenderTarget() +{ + + if (m_window == nullptr) { + throw vpException(vpException::fatalError, "Cannot setup render target when window is null"); + } + FrameBufferProperties fbp = getBufferProperties(); + WindowProperties win_prop; + win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); + + // Don't open a window - force it to be an offscreen buffer. + int flags = GraphicsPipe::BF_refuse_window | GraphicsPipe::BF_resizeable; + GraphicsOutput *windowOutput = m_window->get_graphics_output(); + GraphicsEngine *engine = windowOutput->get_engine(); + GraphicsStateGuardian *gsg = windowOutput->get_gsg(); + GraphicsPipe *pipe = windowOutput->get_pipe(); + m_buffer = engine->make_output(pipe, m_name, m_renderOrder, + fbp, win_prop, flags, + gsg, windowOutput); + if (m_buffer == nullptr) { + throw vpException(vpException::fatalError, "Could not create buffer"); + } + m_buffers.push_back(m_buffer); + m_buffer->set_inverted(gsg->get_copy_texture_inverted()); + m_texture = new Texture(); + //fbp.setup_color_texture(m_texture); + m_buffer->add_render_texture(m_texture, m_isOutput ? GraphicsOutput::RenderTextureMode::RTM_copy_ram : GraphicsOutput::RenderTextureMode::RTM_copy_texture); + m_buffer->set_clear_color(LColor(0.f)); + m_buffer->set_clear_color_active(true); + DisplayRegion *region = m_buffer->make_display_region(); + if (region == nullptr) { + throw vpException(vpException::fatalError, "Could not create display region"); + } + region->set_camera(m_cameraPath); + region->set_clear_color(LColor(0.f)); +} + +void vpPanda3DPostProcessFilter::setRenderParameters(const vpPanda3DRenderParameters ¶ms) +{ + m_renderParameters = params; + unsigned int previousH = m_renderParameters.getImageHeight(), previousW = m_renderParameters.getImageWidth(); + bool resize = previousH != params.getImageHeight() || previousW != params.getImageWidth(); + + m_renderParameters = params; + + if (resize) { + for (GraphicsOutput *buffer: m_buffers) { + //buffer->get_type().is_derived_from() + GraphicsBuffer *buf = dynamic_cast(buffer); + if (buf == nullptr) { + throw vpException(vpException::fatalError, "Panda3D: could not cast to GraphicsBuffer when rendering."); + } + else { + buf->set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); + } + } + } +} + +#endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 775e8612a4..257c0ba760 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -267,7 +267,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() GraphicsEngine *engine = windowOutput->get_engine(); GraphicsStateGuardian *gsg = windowOutput->get_gsg(); GraphicsPipe *pipe = windowOutput->get_pipe(); - m_colorBuffer = engine->make_output(pipe, "Color Buffer", -100, + m_colorBuffer = engine->make_output(pipe, "Color Buffer", m_renderOrder, fbp, win_prop, flags, gsg, windowOutput); if (m_colorBuffer == nullptr) { diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 991031dd4e..5002a113e3 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -78,7 +78,9 @@ void vpPanda3DRendererSet::initFramework() void vpPanda3DRendererSet::setCameraPose(const vpHomogeneousMatrix &wTc) { for (std::shared_ptr &renderer: m_subRenderers) { - renderer->setCameraPose(wTc); + if (renderer->isRendering3DScene()) { + renderer->setCameraPose(wTc); + } } } @@ -87,13 +89,18 @@ vpHomogeneousMatrix vpPanda3DRendererSet::getCameraPose() if (m_subRenderers.size() == 0) { throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); } + if (!m_subRenderers[0]->isRendering3DScene()) { + throw vpException(vpException::fatalError, "This renderer set only contains a non-3D renderer."); + } return m_subRenderers[0]->getCameraPose(); } void vpPanda3DRendererSet::setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) { for (std::shared_ptr &renderer: m_subRenderers) { - renderer->setNodePose(name, wTo); + if (renderer->isRendering3DScene()) { + renderer->setNodePose(name, wTo); + } } } @@ -107,6 +114,9 @@ vpHomogeneousMatrix vpPanda3DRendererSet::getNodePose(const std::string &name) if (m_subRenderers.size() == 0) { throw vpException(vpException::fatalError, "cannot get the pose of an object if no subrenderer is present"); } + if (!m_subRenderers[0]->isRendering3DScene()) { + throw vpException(vpException::fatalError, "This renderer set only contains a non-3D renderer."); + } return m_subRenderers[0]->getNodePose(name); } @@ -118,7 +128,9 @@ vpHomogeneousMatrix vpPanda3DRendererSet::getNodePose(NodePath &object) void vpPanda3DRendererSet::addNodeToScene(const NodePath &object) { for (std::shared_ptr &renderer: m_subRenderers) { - renderer->addNodeToScene(object); + if (renderer->isRendering3DScene()) { + renderer->addNodeToScene(object); + } } } @@ -147,7 +159,19 @@ void vpPanda3DRendererSet::addSubRenderer(std::shared_ptr throw vpException(vpException::badValue, "Cannot have two subrenderers with the same name"); } } - m_subRenderers.push_back(renderer); + std::vector>::const_iterator it = m_subRenderers.begin(); + while (it != m_subRenderers.end()) { + if ((*it)->getRenderOrder() > renderer->getRenderOrder()) { + break; + } + ++it; + } + m_subRenderers.insert(it, renderer); + for (const auto r: m_subRenderers) { + std::cout << r->getName() << " "; + } + std::cout << std::endl; + renderer->setRenderParameters(m_renderParameters); if (m_framework != nullptr) { renderer->initFromParent(m_framework, m_window); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index c85a05be44..2165bb8044 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -17,6 +17,7 @@ #include #include #include +#include void displayNormals(const vpImage &normalsImage, vpImage &normalDisplayImage) @@ -63,6 +64,7 @@ int main(int argc, const char **argv) bool stepByStep = false; bool debug = false; bool showLightContrib = false; + bool showCanny = false; char *modelPathCstr = nullptr; vpParseArgv::vpArgvInfo argTable[] = { @@ -74,6 +76,9 @@ int main(int argc, const char **argv) "Show frames step by step."}, {"-specular", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&showLightContrib, "Show frames step by step."}, + {"-canny", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&showCanny, + "Show frames step by step."}, + {"-debug", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&debug, "Show Opengl/Panda3D debug message."}, {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, @@ -106,6 +111,8 @@ int main(int argc, const char **argv) std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); std::shared_ptr rgbDiffuseRenderer = std::shared_ptr(new vpPanda3DRGBRenderer(false)); + std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, true); + renderer.addSubRenderer(geometryRenderer); renderer.addSubRenderer(cameraRenderer); @@ -113,7 +120,9 @@ int main(int argc, const char **argv) if (showLightContrib) { renderer.addSubRenderer(rgbDiffuseRenderer); } - + if (showCanny) { + renderer.addSubRenderer(grayscaleFilter); + } renderer.setVerticalSyncEnabled(false); renderer.setAbortOnPandaError(true); @@ -149,6 +158,7 @@ int main(int argc, const char **argv) vpImage colorImage(h, w); vpImage colorDiffuseOnly(h, w); vpImage lightDifference(h, w); + vpImage cannyImage(h, w); vpImage normalDisplayImage(h, w); vpImage cameraNormalDisplayImage(h, w); @@ -173,9 +183,12 @@ int main(int argc, const char **argv) DisplayCls dImageDiff; if (showLightContrib) { - dImageDiff.init(lightDifference, w * 2 + 90, 0, "Specular/reflectance contribution"); + dImageDiff.init(lightDifference, w * 2 + 80, 0, "Specular/reflectance contribution"); + } + DisplayCls dCanny; + if (showCanny) { + dCanny.init(cannyImage, w * 2 + 80, h + 80, "Canny"); } - renderer.renderFrame(); bool end = false; bool firstFrame = true; @@ -197,6 +210,9 @@ int main(int argc, const char **argv) if (showLightContrib) { renderer.getRenderer(rgbDiffuseRenderer->getName())->getRender(colorDiffuseOnly); } + if (showCanny) { + renderer.getRenderer()->getRender(cannyImage); + } const double beforeConvert = vpTime::measureTimeMs(); @@ -206,6 +222,11 @@ int main(int argc, const char **argv) if (showLightContrib) { displayLightDifference(colorImage, colorDiffuseOnly, lightDifference); } + vpDisplay::display(cannyImage); + vpDisplay::flush(cannyImage); + + + vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); From 2e6818254bb959c9366b805edd203c627698a9f7 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Tue, 30 Apr 2024 17:24:32 +0200 Subject: [PATCH 51/65] fix texture rendering --- .../vpPanda3DCommonFilters.cpp | 2 +- .../vpPanda3DRGBRenderer.cpp | 25 ++++++++++++------- tutorial/ar/tutorial-panda3d-renderer.cpp | 4 +-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index 98062831e4..22d5dfe93d 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -8,9 +8,9 @@ const char *vpPanda3DLuminanceFilter::FRAGMENT_SHADER = R"shader( in vec2 texcoords; +uniform sampler2D p3d_Texture0; out vec4 p3d_FragData; -uniform sampler2D p3d_Texture0; void main() { vec4 v = texture(p3d_Texture0, texcoords); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 257c0ba760..d0c75e45ac 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -152,7 +152,14 @@ void main() vec3 v = normalize(-viewVertex.xyz); // normalized view vector float nv = max(0.f, dot(n, v)); float roughness2 = pow(p3d_Material.roughness, 2); - p3d_FragData = p3d_LightModel.ambient * p3d_Material.ambient; + + #ifdef HAS_TEXTURE + vec4 baseColor = texture(p3d_Texture0, texcoords); + #else + vec4 baseColor = p3d_Material.baseColor; + #endif + //p3d_FragData = vec4(0.0, 0.0, 0.0, 1.f); + p3d_FragData = p3d_LightModel.ambient * baseColor; for(int i = 0; i < p3d_LightSource.length(); ++i) { @@ -170,13 +177,8 @@ void main() vec3 FV = F(F0, vh); vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); - #ifdef HAS_TEXTURE - vec4 diffuseColor = texture(p3d_Texture0, texcoords); - #else - vec4 diffuseColor = p3d_Material.baseColor; - #endif - + vec4 diffuseColor = baseColor; #ifdef SPECULAR vec3 specularColor = vec3(0.f, 0.f, 0.f); @@ -184,7 +186,7 @@ void main() float DV = D(roughness2, hn); float GV = G(hn, nv, nl, vh); vec3 rs = (DV * GV * FV) / (4.f * nl * nv); - specularColor = rs * p3d_Material.baseColor.rgb; + specularColor = rs * p3d_Material.specular; } #else vec3 specularColor = vec3(0.0, 0.0, 0.0); @@ -205,6 +207,9 @@ std::string vpPanda3DRGBRenderer::makeFragmentShader(bool hasTexture, bool specu if (specular) { ss << "#define SPECULAR 1" << std::endl; } + else { + ss << "#undef SPECULAR" << std::endl; + } ss << vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG; std::cout << ss.str() << std::endl; return ss.str(); @@ -214,9 +219,10 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); + std::cout << "SHOW SPECULARS = " << m_showSpeculars << std::endl; PT(Shader) shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, COOK_TORRANCE_VERT, - makeFragmentShader(false, m_showSpeculars)); + makeFragmentShader(true, m_showSpeculars)); objectInScene.set_shader(shader); @@ -277,6 +283,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() m_colorBuffer->set_inverted(gsg->get_copy_texture_inverted()); m_colorTexture = new Texture(); fbp.setup_color_texture(m_colorTexture); + //m_colorTexture->set_format(Texture::Format::F_srgb_alpha); m_colorBuffer->add_render_texture(m_colorTexture, GraphicsOutput::RenderTextureMode::RTM_copy_ram); m_colorBuffer->set_clear_color(LColor(0.f)); m_colorBuffer->set_clear_color_active(true); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 2165bb8044..885c1365c2 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -100,7 +100,7 @@ int main(int argc, const char **argv) else { modelPath = "data/deformed_sphere.bam"; } - vpPanda3DRenderParameters renderParams(vpCameraParameters(600, 600, 320, 240), 480, 640, 0.01, 10.0); + vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -144,7 +144,7 @@ int main(int argc, const char **argv) vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.2, -0.4 }), vpColVector({ 0.0, 0.0, 1.0 })); + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.1, -0.1 }), vpColVector({ 0.0, 0.0, 2.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); From a066aa31e3000bc4937ac8e2d2a43452c5026237 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Thu, 2 May 2024 15:18:48 +0200 Subject: [PATCH 52/65] Add some documentation, grouping panda3D related classes --- doc/mainpage.dox.in | 6 +++ .../include/visp3/ar/vpPanda3DBaseRenderer.h | 4 +- .../visp3/ar/vpPanda3DGeometryRenderer.h | 2 + modules/ar/include/visp3/ar/vpPanda3DLight.h | 7 +++- .../include/visp3/ar/vpPanda3DRendererSet.h | 39 +++++++++++++++++-- .../vpPanda3DCommonFilters.cpp | 1 - .../vpPanda3DRendererSet.cpp | 2 - 7 files changed, 52 insertions(+), 9 deletions(-) diff --git a/doc/mainpage.dox.in b/doc/mainpage.dox.in index 4bee5562af..e282ff4146 100644 --- a/doc/mainpage.dox.in +++ b/doc/mainpage.dox.in @@ -376,6 +376,12 @@ in different ways. This will motivate us to continue the efforts. \defgroup group_ar_renderer Renderer Renderer interfaces. */ +/*! + \ingroup group_ar_renderer + \defgroup group_ar_renderer_panda3d Panda3D Renderer + Modular renderer based on the Panda3D renderer. +*/ + /*! \ingroup module_ar \defgroup group_ar_simulator Simulator diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index cf9594fa39..1853004bd0 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -43,7 +43,9 @@ #include /** - * @brief Base class for a panda3D renderer. This class handles basic functionalities, + * \ingroup group_ar_renderer_panda3d + * + * \brief Base class for a panda3D renderer. This class handles basic functionalities, * such as loading object, changing camera parameters. * * For a subclass to have a novel behaviour (e.g, display something else) These methods should be overriden: diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index ecf96705ff..9b7b4c5544 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -40,6 +40,8 @@ #include /** + * \ingroup group_ar_renderer_panda3d + * * @brief Renderer that outputs object geometric information. * * This information may contain, depending on requested render type: diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index 2362549acc..543db77af1 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -46,7 +46,9 @@ #include "pointLight.h" /** - * @brief Base class for a Light that can be added to a Panda3D scene. + * \ingroup group_ar_renderer_panda3d + * + * \brief Base class for a Light that can be added to a Panda3D scene. * * Note that modifying any object that inherits from this class * after the method addToScene has been called * will not update the rendered light. @@ -92,6 +94,9 @@ class VISP_EXPORT vpPanda3DLight }; /** + * + * \ingroup group_ar_renderer_panda3d + * * @brief Class representing an ambient light. * * Ambient light are not physically possible, but are used to emulate light coming from all directions. diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index 6602d7b5b6..2684f37987 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -40,11 +40,28 @@ #include #include +/** + * \ingroup group_ar_renderer_panda3d + * + * @brief Class that rendering multiple datatypes, in a single pass. A RendererSet contains multiple subrenderers, all inheriting from vpPanda3DBaseRenderer. + * The renderer set synchronizes all scene properties for the different subrenderers. This includes: + * * The camera properties (intrinsics, resolution) and extrinsics + * * The pose and properties of every object in the scene + * * The pose and properties of lights, for subrenderers that are defined as lightable. + * + * The overall usage workflow is the following: + * 1. Create vpPanda3DRendererSet instance + * 2. Create the subrenderers (e.g, vpPanda3DGeometryRenderer) + * 3. Add the subrenderers to the set with addSubRenderer + * 4. Call renderFrame() on the rendererSet. Each subrenderer now has its output computed and ready to be retrieved + * 5. Retrieve relevant outputs in ViSP format with something similar to `rendererSet.getRenderer("MyRendererName").getRender(I)` where RendererType is the relevant subclass of vpPanda3DBaseRenderer and "MyRendererName" its name (see vpPanda3DBaseRenderer::getName) + */ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vpPanda3DLightable { public: vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters); - virtual ~vpPanda3DRendererSet(); + + virtual ~vpPanda3DRendererSet() = default; /** * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers. @@ -122,12 +139,20 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp void addLight(const vpPanda3DLight &light) vp_override; /** - * @brief Add a new subrenderer + * @brief Add a new subrenderer: This subrenderer should have a unique name, not present in the set. + * + * \throws if the subrenderer's name is already present in the set. * - * @param renderer + * @param renderer the renderer to add */ void addSubRenderer(std::shared_ptr renderer); + /** + * @brief Retrieve the first subrenderer with the specified template type. + * + * @tparam RendererType The type of the renderer to find + * @return std::shared_ptr Pointer to the first renderer match, nullptr if none is found. + */ template std::shared_ptr getRenderer() { @@ -139,7 +164,13 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp } return nullptr; } - + /** + * @brief Retrieve the subrenderer with the specified template type and the given name. + * + * @param name the name of the subrenderer to find + * @tparam RendererType The type of the renderer to find + * @return std::shared_ptr Pointer to the renderer, nullptr if none is found. + */ template std::shared_ptr getRenderer(const std::string &name) { diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index 22d5dfe93d..2ae3b14bb9 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -16,7 +16,6 @@ void main() { vec4 v = texture(p3d_Texture0, texcoords); p3d_FragData.b = 0.299 * v.r + 0.587 * v.g + 0.114 * v.b; } - )shader"; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 5002a113e3..af16455394 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -40,8 +40,6 @@ vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &rend m_renderParameters = renderParameters; } -vpPanda3DRendererSet::~vpPanda3DRendererSet() -{ } void vpPanda3DRendererSet::initFramework() { From be943e8dc00e7834a8737d1f0cf387a241cdc82d Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 3 May 2024 13:01:16 +0200 Subject: [PATCH 53/65] Add canny and gaussian blur filters --- .../include/visp3/ar/vpPanda3DCommonFilters.h | 40 +++- .../visp3/ar/vpPanda3DPostProcessFilter.h | 8 +- .../vpPanda3DCommonFilters.cpp | 173 +++++++++++++++++- .../vpPanda3DPostProcessFilter.cpp | 11 +- .../vpPanda3DRGBRenderer.cpp | 18 +- .../vpPanda3DRendererSet.cpp | 2 + tutorial/ar/tutorial-panda3d-renderer.cpp | 43 +++-- 7 files changed, 261 insertions(+), 34 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h index 6fbf7abab1..5f01ab0870 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h @@ -9,16 +9,54 @@ class vpPanda3DRGBRenderer; +/** + * @brief Class that implements an RGB to grayscale conversion. + * + */ class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter { public: - vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput); + vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); FrameBufferProperties getBufferProperties() const vp_override; void getRender(vpImage &I) const; private: static const char *FRAGMENT_SHADER; +}; + +/** + * @brief Class that implements a gaussian filter on a grayscale image. + * The grayscale image should be contained in the blue channel of the image. + * + */ +class VISP_EXPORT vpPanda3DGaussianBlur : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DGaussianBlur(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); + FrameBufferProperties getBufferProperties() const vp_override; + void getRender(vpImage &I) const; + +private: + static const char *FRAGMENT_SHADER; +}; + +class VISP_EXPORT vpPanda3DCanny : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DCanny(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, float edgeThreshold); + FrameBufferProperties getBufferProperties() const vp_override; + void getRender(vpImage &I) const; + void setEdgeThreshold(float edgeThreshold); + +protected: + void setupScene() vp_override; + +private: + static const char *FRAGMENT_SHADER; + float m_edgeThreshold; }; + + #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h index 963ac815dc..707d7500f2 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -12,7 +12,7 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer { public: - vpPanda3DPostProcessFilter(const std::string &name, const std::shared_ptr &inputRenderer, bool isOutput, std::string fragmentShader) + vpPanda3DPostProcessFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, std::string fragmentShader) : vpPanda3DBaseRenderer(name), m_inputRenderer(inputRenderer), m_isOutput(isOutput), m_fragmentShader(fragmentShader) { m_renderOrder = m_inputRenderer->getRenderOrder() + 1; @@ -22,8 +22,10 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer return false; } + GraphicsOutput *getMainOutputBuffer() vp_override { return m_buffer; } + protected: - void setupScene() vp_override; + virtual void setupScene() vp_override; void setupCamera() vp_override; @@ -36,7 +38,7 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer std::shared_ptr m_inputRenderer; bool m_isOutput; //! Whether this filter is an output to be used and should be copied to ram std::string m_fragmentShader; - PT(Shader) shader; + PT(Shader) m_shader; Texture *m_texture; GraphicsOutput *m_buffer; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index 22d5dfe93d..95e8e5dc28 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -20,12 +20,78 @@ void main() { )shader"; -vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput) +vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput) : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, std::string(vpPanda3DLuminanceFilter::FRAGMENT_SHADER)) +{ } +FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const { + FrameBufferProperties fbp; + fbp.set_depth_bits(0); + fbp.set_rgba_bits(0, 0, 8, 0); + fbp.set_float_color(false); + return fbp; +} +void vpPanda3DLuminanceFilter::getRender(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); + if (indexMultiplier != 1) { + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i] = data[i * indexMultiplier]; + } + } + else { + memcpy(I.bitmap, &data[0], I.getSize() * sizeof(unsigned char)); + } +} + + +const char *vpPanda3DGaussianBlur::FRAGMENT_SHADER = R"shader( +#version 330 + +in vec2 texcoords; + +uniform sampler2D p3d_Texture0; +uniform vec2 dp; // 1 divided by number of pixels + +const float kernel[25] = float[25]( + 2, 4, 5, 4, 2, + 4, 9, 12, 9, 4, + 5, 12, 15, 12, 5, + 4, 9, 12, 9, 4, + 2, 4, 5, 4, 2 +); +const float normalize = 1 / 159.0; + +vec2 offset[25] = vec2[25]( + vec2(-2*dp.x,-2*dp.y), vec2(-dp.x,-2*dp.y), vec2(0,-2*dp.y), vec2(dp.x,-2*dp.y), vec2(2*dp.x,-2*dp.y), + vec2(-2*dp.x,-dp.y), vec2(-dp.x, -dp.y), vec2(0.0, -dp.y), vec2(dp.x, -dp.y), vec2(2*dp.x,-dp.y), + vec2(-2*dp.x,0.0), vec2(-dp.x, 0.0), vec2(0.0, 0.0), vec2(dp.x, 0.0), vec2(2*dp.x,0.0), + vec2(-2*dp.x, dp.y), vec2(-dp.x, dp.y), vec2(0.0, dp.y), vec2(dp.x, dp.y), vec2(2*dp.x, dp.y), + vec2(-2*dp.x, 2*dp.y), vec2(-dp.x, 2*dp.y), vec2(0.0, 2*dp.y), vec2(dp.x, 2*dp.y), vec2(2*dp.x, 2*dp.y) +); +out vec4 p3d_FragData; + +void main() { + float v = 0.f; + + for(int i = 0; i < 25; ++i) { + v += kernel[i] * texture(p3d_Texture0, texcoords + offset[i]).b ; + } + p3d_FragData.b = v * normalize; } -FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const +)shader"; + +vpPanda3DGaussianBlur::vpPanda3DGaussianBlur(const std::string &name, std::shared_ptr inputRenderer, bool isOutput) + : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, vpPanda3DGaussianBlur::FRAGMENT_SHADER) +{ } + +FrameBufferProperties vpPanda3DGaussianBlur::getBufferProperties() const { FrameBufferProperties fbp; fbp.set_depth_bits(0); @@ -33,13 +99,14 @@ FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const fbp.set_float_color(false); return fbp; } -void vpPanda3DLuminanceFilter::getRender(vpImage &I) const + +void vpPanda3DGaussianBlur::getRender(vpImage &I) const { if (!m_isOutput) { throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); } unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image - I.resize(m_texture->get_y_size(), m_texture->get_x_size()); + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); if (indexMultiplier != 1) { for (unsigned int i = 0; i < I.getSize(); ++i) { @@ -51,6 +118,104 @@ void vpPanda3DLuminanceFilter::getRender(vpImage &I) const } } +const char *vpPanda3DCanny::FRAGMENT_SHADER = R"shader( +#version 330 + +in vec2 texcoords; + +uniform sampler2D p3d_Texture0; +uniform vec2 dp; // 1 divided by number of pixels +uniform float edgeThreshold; + +const float kernel[9] = float[9]( + 0.0, 1.0, 0.0, + 1.0,-4.0, 1.0, + 0.0, 1.0, 0.0 +); + +const float kernel_h[9] = float[9]( + -1.0, 0.0, 1.0, + -2.0, 0.0, 2.0, + -1.0, 0.0, 1.0 +); + +const float kernel_v[9] = float[9]( + -1.0, -2.0, -1.0, + 0.0, 0.0, 0.0, + 1.0, 2.0, 1.0 +); + +vec2 offset[9] = vec2[9]( + vec2(-dp.x, -dp.y), vec2(0.0, -dp.y), vec2(dp.x, -dp.y), + vec2(-dp.x, 0.0), vec2(0.0, 0.0), vec2(dp.x, 0.0), + vec2(-dp.x, dp.y), vec2(0.0, dp.y), vec2(dp.x, dp.y) +); + + +out vec4 p3d_FragData; + +void main() { + float sum = 0.f; + for(int i = 0; i < 9; ++i) { + float pix = texture(p3d_Texture0, texcoords + offset[i]).b; + sum += pix * kernel[i]; + } + if(abs(sum * 255.f) > edgeThreshold) { + float sum_h = 0.f; + float sum_v = 0.f; + for(int i = 0; i < 9; ++i) { + float pix = texture(p3d_Texture0, texcoords + offset[i]).b; + sum_h += pix * kernel_h[i]; + sum_v += pix * kernel_v[i]; + } + + vec2 orientationAndValid = sum_h * sum_h + sum_v * sum_v > 0 ? vec2(atan(sum_v/sum_h), 1.f) : vec2(0.f, 0.f); + p3d_FragData = vec4(sum_h, sum_v, orientationAndValid.x, orientationAndValid.y); + } else { + p3d_FragData = vec4(0.f, 0.f, 0.f, 0.f); + } +} +)shader"; + +vpPanda3DCanny::vpPanda3DCanny(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, float edgeThreshold) + : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, vpPanda3DCanny::FRAGMENT_SHADER), m_edgeThreshold(edgeThreshold) +{ } + +void vpPanda3DCanny::setupScene() +{ + vpPanda3DPostProcessFilter::setupScene(); + m_renderRoot.set_shader_input("edgeThreshold", LVector2f(m_edgeThreshold)); +} +void vpPanda3DCanny::setEdgeThreshold(float edgeThreshold) +{ + m_edgeThreshold = edgeThreshold; + m_renderRoot.set_shader_input("edgeThreshold", LVector2f(m_edgeThreshold)); +} + + +FrameBufferProperties vpPanda3DCanny::getBufferProperties() const +{ + FrameBufferProperties fbp; + fbp.set_depth_bits(0); + fbp.set_rgba_bits(32, 32, 32, 32); + fbp.set_float_color(true); + return fbp; +} + +void vpPanda3DCanny::getRender(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + float *data = (float *)(&(m_texture->get_ram_image().front())); + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i].B = (data[i * 4]); + I.bitmap[i].G = (data[i * 4 + 1]); + I.bitmap[i].R = (data[i * 4 + 2]); + } +} #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp index 662f10011d..4ddd5a933b 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -30,10 +30,11 @@ void vpPanda3DPostProcessFilter::setupScene() throw vpException(vpException::fatalError, "Cannot add a postprocess filter to a renderer that does not define getMainOutputBuffer()"); } - shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + m_shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, FILTER_VERTEX_SHADER, m_fragmentShader); - m_renderRoot.set_shader(shader); + m_renderRoot.set_shader(m_shader); + m_renderRoot.set_shader_input("dp", LVector2f(1.0 / buffer->get_texture()->get_x_size(), 1.0 / buffer->get_texture()->get_y_size())); std::cout << m_fragmentShader << std::endl; m_renderRoot.set_texture(buffer->get_texture()); m_renderRoot.set_attrib(LightRampAttrib::make_identity()); @@ -77,7 +78,6 @@ void vpPanda3DPostProcessFilter::setupRenderTarget() m_buffers.push_back(m_buffer); m_buffer->set_inverted(gsg->get_copy_texture_inverted()); m_texture = new Texture(); - //fbp.setup_color_texture(m_texture); m_buffer->add_render_texture(m_texture, m_isOutput ? GraphicsOutput::RenderTextureMode::RTM_copy_ram : GraphicsOutput::RenderTextureMode::RTM_copy_texture); m_buffer->set_clear_color(LColor(0.f)); m_buffer->set_clear_color_active(true); @@ -96,7 +96,10 @@ void vpPanda3DPostProcessFilter::setRenderParameters(const vpPanda3DRenderParame bool resize = previousH != params.getImageHeight() || previousW != params.getImageWidth(); m_renderParameters = params; - + if (m_window != nullptr) { + GraphicsOutput *buffer = m_inputRenderer->getMainOutputBuffer(); + m_renderRoot.set_shader_input("dp", LVector2f(1.0 / buffer->get_texture()->get_x_size(), 1.0 / buffer->get_texture()->get_y_size())); + } if (resize) { for (GraphicsOutput *buffer: m_buffers) { //buffer->get_type().is_derived_from() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index d0c75e45ac..4135a7e6af 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -85,7 +85,6 @@ const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( // Version 330, specified when generating shader #define M_PI 3.1415926535897932384626433832795 - in vec3 oNormal; in vec4 viewVertex; in vec3 F0; @@ -138,8 +137,6 @@ float G(float hn, float nv, float nl, float vh) return min(1.0, min((2.f * hn * nv) / vh, (2.f * hn * nl) / vh)); } - - vec3 F(vec3 F0, float vh) { return F0 + (vec3(1.f, 1.f, 1.f) - F0) * pow(1.f - vh, 5); @@ -151,14 +148,16 @@ void main() vec3 n = normalize(oNormal); // normalized normal vector vec3 v = normalize(-viewVertex.xyz); // normalized view vector float nv = max(0.f, dot(n, v)); - float roughness2 = pow(p3d_Material.roughness, 2); + float roughness2 = clamp(pow(p3d_Material.roughness, 2), 0.01, 0.99); #ifdef HAS_TEXTURE vec4 baseColor = texture(p3d_Texture0, texcoords); + vec4 ambientColor = baseColor; #else + vec4 ambientColor = p3d_Material.ambient; vec4 baseColor = p3d_Material.baseColor; #endif - //p3d_FragData = vec4(0.0, 0.0, 0.0, 1.f); + p3d_FragData = p3d_LightModel.ambient * baseColor; @@ -176,9 +175,8 @@ void main() float attenuation = 1.f / (aFac[0] + aFac[1] * lightDist + aFac[2] * lightDist * lightDist); vec3 FV = F(F0, vh); - vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); + vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); - vec4 diffuseColor = baseColor; #ifdef SPECULAR vec3 specularColor = vec3(0.f, 0.f, 0.f); @@ -192,7 +190,7 @@ void main() vec3 specularColor = vec3(0.0, 0.0, 0.0); #endif - p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (diffuseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); + p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (baseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); } } )shader"; @@ -219,10 +217,12 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); + const RenderAttrib *textureAttrib = objectInScene.get_attrib(TextureAttrib::get_class_type()); + bool hasTexture = textureAttrib != nullptr; std::cout << "SHOW SPECULARS = " << m_showSpeculars << std::endl; PT(Shader) shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, COOK_TORRANCE_VERT, - makeFragmentShader(true, m_showSpeculars)); + makeFragmentShader(hasTexture, m_showSpeculars)); objectInScene.set_shader(shader); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 5002a113e3..a72f71ab3e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -38,6 +38,7 @@ vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") { m_renderParameters = renderParameters; + load_prc_file_data("", "textures-power-2 none"); } vpPanda3DRendererSet::~vpPanda3DRendererSet() @@ -51,6 +52,7 @@ void vpPanda3DRendererSet::initFramework() load_prc_file_data("", "gl-version 3 2"); + if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 885c1365c2..23439ecd60 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -56,7 +56,19 @@ void displayLightDifference(const vpImage &colorImage, const vpImage &cannyRawData, + vpImage &canny) +{ +#pragma omp parallel for + for (int i = 0; i < cannyRawData.getSize(); ++i) { + vpRGBf &px = cannyRawData.bitmap[i]; + //canny.bitmap[i] = 255 * (px.R * px.R + px.G * px.G > 0); + canny.bitmap[i] = static_cast(127.5f + 127.5f * atan(px.B)); + } + vpDisplay::display(canny); + vpDisplay::flush(canny); +} int main(int argc, const char **argv) { @@ -106,12 +118,13 @@ int main(int argc, const char **argv) const std::string objectName = "object"; - std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS)); - - std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); - std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); - std::shared_ptr rgbDiffuseRenderer = std::shared_ptr(new vpPanda3DRGBRenderer(false)); - std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, true); + std::shared_ptr geometryRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS); + std::shared_ptr cameraRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); + std::shared_ptr rgbRenderer = std::make_shared(); + std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); + std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); + std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); + std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 20.f); renderer.addSubRenderer(geometryRenderer); @@ -122,6 +135,9 @@ int main(int argc, const char **argv) } if (showCanny) { renderer.addSubRenderer(grayscaleFilter); + renderer.addSubRenderer(blurFilter); + renderer.addSubRenderer(cannyFilter); + } renderer.setVerticalSyncEnabled(false); @@ -144,17 +160,19 @@ int main(int argc, const char **argv) vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.1, -0.1 }), vpColVector({ 0.0, 0.0, 2.0 })); + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.4, -0.1 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); + unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); + vpImage normalsImage; vpImage cameraNormalsImage; + vpImage cannyRawData; vpImage depthImage; - unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); vpImage colorImage(h, w); vpImage colorDiffuseOnly(h, w); vpImage lightDifference(h, w); @@ -211,7 +229,7 @@ int main(int argc, const char **argv) renderer.getRenderer(rgbDiffuseRenderer->getName())->getRender(colorDiffuseOnly); } if (showCanny) { - renderer.getRenderer()->getRender(cannyImage); + renderer.getRenderer()->getRender(cannyRawData); } const double beforeConvert = vpTime::measureTimeMs(); @@ -222,10 +240,9 @@ int main(int argc, const char **argv) if (showLightContrib) { displayLightDifference(colorImage, colorDiffuseOnly, lightDifference); } - vpDisplay::display(cannyImage); - vpDisplay::flush(cannyImage); - - + if (showCanny) { + displayCanny(cannyRawData, cannyImage); + } vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red); From 4f84919ae4bde9c8d4ca9d41d4cac987a44ab436 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 3 May 2024 16:12:54 +0200 Subject: [PATCH 54/65] Fix canny display, directional lighting, documentation and class goruping --- doc/mainpage.dox.in | 17 ++- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 2 + .../include/visp3/ar/vpPanda3DCommonFilters.h | 18 ++- modules/ar/include/visp3/ar/vpPanda3DLight.h | 111 ++++++++++++++---- .../visp3/ar/vpPanda3DPostProcessFilter.h | 15 +++ .../include/visp3/ar/vpPanda3DRGBRenderer.h | 34 ++++++ .../vpPanda3DBaseRenderer.cpp | 6 +- .../vpPanda3DPostProcessFilter.cpp | 1 + .../vpPanda3DRGBRenderer.cpp | 16 ++- tutorial/ar/tutorial-panda3d-renderer.cpp | 55 ++++++++- 10 files changed, 235 insertions(+), 40 deletions(-) diff --git a/doc/mainpage.dox.in b/doc/mainpage.dox.in index e282ff4146..85ff052fe6 100644 --- a/doc/mainpage.dox.in +++ b/doc/mainpage.dox.in @@ -379,7 +379,22 @@ in different ways. This will motivate us to continue the efforts. /*! \ingroup group_ar_renderer \defgroup group_ar_renderer_panda3d Panda3D Renderer - Modular renderer based on the Panda3D renderer. + Modular renderers based on the Panda3D framework. +*/ +/*! + \ingroup group_ar_renderer_panda3d + \defgroup group_ar_renderer_panda3d_3d 3D scene renderers + Classes that render a 3D scene. +*/ +/*! + \ingroup group_ar_renderer_panda3d + \defgroup group_ar_renderer_panda3d_filters Image processing + Shader-based image processing and filtering. +*/ +/*! + \ingroup group_ar_renderer_panda3d + \defgroup group_ar_renderer_panda3d_lighting Lighting related classes + Light and Lightable implementations. */ /*! diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 1853004bd0..649469def4 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -247,6 +247,8 @@ class VISP_EXPORT vpPanda3DBaseRenderer void enableDebugLog(); static vpColVector vispPointToPanda(const vpColVector &point); + static vpColVector vispVectorToPanda(const vpColVector &vec); + void printStructure(); diff --git a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h index 5f01ab0870..647a8a2249 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h @@ -10,7 +10,8 @@ class vpPanda3DRGBRenderer; /** - * @brief Class that implements an RGB to grayscale conversion. + * \ingroup group_ar_renderer_panda3d_filters + * \brief Class that implements an RGB to grayscale conversion. * */ class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter @@ -25,7 +26,9 @@ class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter }; /** - * @brief Class that implements a gaussian filter on a grayscale image. + * + * \ingroup group_ar_renderer_panda3d_filters + * \brief Class that implements a gaussian filter on a grayscale image. * The grayscale image should be contained in the blue channel of the image. * */ @@ -40,7 +43,16 @@ class VISP_EXPORT vpPanda3DGaussianBlur : public vpPanda3DPostProcessFilter static const char *FRAGMENT_SHADER; }; - +/** + * \ingroup group_ar_renderer_panda3d_filters + * \brief Implementation of canny filtering, using Sobel kernels. + * + * The results of the canny are filtered based on a threshold value (defined between 0 and 255), checking whether there is enough gradient information. + * The output of this image is a floating RGB image containing: + * - In the red channel, the value of the convolution with the sobel horizontal kernel + * - In the green channel, the value of the convolution with the sobel vertical kernel + * - In the blue channel, the angle (in radians) of the edge normal. + */ class VISP_EXPORT vpPanda3DCanny : public vpPanda3DPostProcessFilter { public: diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index 543db77af1..3836e61257 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -40,13 +40,15 @@ #include #include #include + #include "nodePath.h" #include "ambientLight.h" #include "directionalLight.h" #include "pointLight.h" +#include "directionalLight.h" /** - * \ingroup group_ar_renderer_panda3d + * \ingroup group_ar_renderer_panda3d_lighting * * \brief Base class for a Light that can be added to a Panda3D scene. * @@ -60,31 +62,31 @@ class VISP_EXPORT vpPanda3DLight { public: /** - * @brief Build a new Panda3D light, given a unique name and an RGB color. + * \brief Build a new Panda3D light, given a unique name and an RGB color. * * - * @param name the name of the light: should be unique in the scene where the light will be added. - * @param color The color of the light: Each R,G,B component is unbounded and can exceed a value of 1 to increase its intensity. + * \param name the name of the light: should be unique in the scene where the light will be added. + * \param color The color of the light: Each R,G,B component is unbounded and can exceed a value of 1 to increase its intensity. */ vpPanda3DLight(const std::string &name, const vpRGBf &color) : m_name(name), m_color(color) { } /** - * @brief Get the name of the light. + * \brief Get the name of the light. * * This name should be unique and will be required when interacting with Panda3D to fetch the node. */ const std::string &getName() const { return m_name; } /** - * @brief Get the light's color + * \brief Get the light's color * - * @return const vpRGBf& + * \return const vpRGBf& */ const vpRGBf &getColor() const { return m_color; } /** - * @brief Add the light to the scene. + * \brief Add the light to the scene. * - * @param scene Scene where the light should be added. + * \param scene Scene where the light should be added. */ virtual void addToScene(NodePath &scene) const = 0; @@ -95,9 +97,9 @@ class VISP_EXPORT vpPanda3DLight /** * - * \ingroup group_ar_renderer_panda3d + * \ingroup group_ar_renderer_panda3d_lighting * - * @brief Class representing an ambient light. + * \brief Class representing an ambient light. * * Ambient light are not physically possible, but are used to emulate light coming from all directions. * They do not generate speculars or reflections. @@ -119,22 +121,25 @@ class VISP_EXPORT vpPanda3DAmbientLight : public vpPanda3DLight }; /** - * @brief Class representing a Point Light. + * \ingroup group_ar_renderer_panda3d_lighting + * + * \brief Class representing a Point Light. * * Point lights emit light all around them, from a single point. + * Their light can be subject to a distance-based attenuation. */ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight { public: /** - * @brief Build a new point light. + * \brief Build a new point light. * * \see vpPanda3DLight constructor. * - * @param name name of the light - * @param color color of the light - * @param position Position in the scene of the light. Uses ViSP coordinates. - * @param attenuation Attenuation components of the light as a function of distance. + * \param name name of the light + * \param color color of the light + * \param position Position in the scene of the light. Uses ViSP coordinates. + * \param attenuation Attenuation components of the light as a function of distance. * Should be a vector of size 3 where the first component is the constant intensity factor (no falloff), * the second is a linear falloff coefficient, and the last one is the quadratic falloff component. * To follow the inverse square law, set this value vector to [0, 0, 1] @@ -160,8 +165,8 @@ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); light->set_attenuation(LVecBase3(m_attenuation[0], m_attenuation[1], m_attenuation[2])); NodePath np = scene.attach_new_node(light); - vpColVector posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); - np.set_pos(posPanda[0], posPanda[1], posPanda[2]); + //vpColVector posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); + np.set_pos(m_position[0], m_position[1], m_position[2]); scene.set_light(np); } @@ -169,9 +174,54 @@ class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight vpColVector m_position; //! Position of the light, in homogeneous coordinates vpColVector m_attenuation; //! Attenuation components: [constant, linear, quadratic] }; +/** + * + * + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Class representing a directional light + * + * A directional light has no origin nor falloff. + * + */ +class VISP_EXPORT vpPanda3DDirectionalLight : public vpPanda3DLight +{ +public: + /** + * \brief Build a new directional light. + * + * \see vpPanda3DLight constructor. + * + * \param name name of the light + * \param color color of the light + * \param direction Position in the scene of the light. Uses ViSP coordinates. + */ + vpPanda3DDirectionalLight(const std::string &name, const vpRGBf &color, const vpColVector &direction) + : vpPanda3DLight(name, color), m_direction(direction) + { + if (m_direction.size() != 3) { + throw vpException(vpException::dimensionError, "Direction light direction must be a 3 dimensional vector"); + } + m_direction.normalize(); + } + + void addToScene(NodePath &scene) const vp_override + { + PT(DirectionalLight) light = new DirectionalLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + vpColVector dir = vpPanda3DBaseRenderer::vispVectorToPanda(m_direction); + std::cout << m_direction << ", " << dir << std::endl; + light->set_direction(LVector3f(m_direction[0], m_direction[1], m_direction[2])); + NodePath np = scene.attach_new_node(light); + scene.set_light(np); + } + +private: + vpColVector m_direction; //! Direction vector of the light, in scene frame +}; /** - * @brief Interface for objects, scenes or other Panda3D related data that can be lit by a vpPanda3DLight. + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Interface for objects, scenes or other Panda3D related data that can be lit by a vpPanda3DLight. * */ class VISP_EXPORT vpPanda3DLightable @@ -179,13 +229,18 @@ class VISP_EXPORT vpPanda3DLightable public: virtual ~vpPanda3DLightable() = default; /** - * @brief Light this lightable object with a new light + * \brief Light this lightable object with a new light * - * @param light + * \param light */ virtual void addLight(const vpPanda3DLight &light) = 0; }; - +/** + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Implementation of vpPanda3DLightable for a panda scene with a root node. + * + * The root node should be specified with setLightableScene by an inheriting implementation. + */ class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable { public: @@ -195,8 +250,12 @@ class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable vpPanda3DLightableScene(NodePath &scene) : vpPanda3DLightable(), m_lightableScene(scene) { } - - + /** + * \brief Add a light to the scene. All of the objects in the scene will be lit. + * + * \throws if the scene is not setup (setLightableScene or constructor with NodePath has not been called) + * \param light light to add + */ void addLight(const vpPanda3DLight &light) vp_override { if (m_lightableScene.is_empty()) { @@ -207,7 +266,7 @@ class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable protected: void setLightableScene(NodePath &scene) { m_lightableScene = scene; } private: - NodePath m_lightableScene; + NodePath m_lightableScene; //! Scene that should be lit when calling addLight }; diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h index 707d7500f2..dfe8a424e4 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -9,6 +9,20 @@ #include "orthographicLens.h" +/** + * \ingroup group_ar_renderer_panda3d_filters + * \brief Base class for postprocessing filters that map the result of a vpPanda3DBaseRenderer to a new image. + * + * Unlike 3D renderers, implementations of this class do not have access to 3D information + * (except if it is the result of the processed image). + * + * Implementation wise, the process is the following: + * + * - The output texture (retrieved through vpPanda3DBaseRenderer::getMainOutputBuffer) is blitted on a quad, + * that is placed perfectly in front of the camera. + * - A shader (given as an argument to the constructor) is applied to this quad. + * - The result is copied back to ram if required. + */ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer { public: @@ -17,6 +31,7 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer { m_renderOrder = m_inputRenderer->getRenderOrder() + 1; } + bool isRendering3DScene() const vp_override { return false; diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 39f6431ae2..f2402658b6 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -40,10 +40,42 @@ #include #include +/** + * \ingroup group_ar_renderer_panda3d_3d + * \brief Implementation of a traditional RGB renderer in Panda3D + * + * The lighting model follows a Cook-torrance BRDF. + * + * For each object, a specific Version of the cook-torrance shader is compiled: diffuse textures are supported, but normal/bump/roughness maps are not. + * This class will try to automatically detect whether an object has RGB textures. + * + * Specular highlights and reflections can be ignored, depending on the value of isShowingSpeculars. + * + * \warning if an object is detected as having image textures but it actually doesn't have any, the object may appear washed out. + * + * \note Most of the tested objects were in BAM format, Panda3D's own format. + * The following pipeline was used: + * - Export to GLTF with Blender + * - In a Python environment, install gltf2bam with `pip install panda3d-gltf` + * - run gltf2bam path/to/yourObject.gltf path/to/yourObject.bam + * - then, in the code, use `renderer.addNodeToScene("/path/to/yourObject.bam");` + * + */ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vpPanda3DLightableScene { public: + /** + * \brief Default constructor. Initialize an RGB renderer with the normal rendering behavior showing speculars + * + */ vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB"), m_showSpeculars(true) { } + /** + * \brief RGB renderer constructor allowing to specify + * whether specular highlights should be rendered or + * if only ambient/diffuse lighting should be considered. + * + * \param showSpeculars whether to render speculars + */ vpPanda3DRGBRenderer(bool showSpeculars) : vpPanda3DBaseRenderer(showSpeculars ? "RGB" : "RGB-diffuse"), m_showSpeculars(showSpeculars) { } @@ -60,6 +92,8 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp GraphicsOutput *getMainOutputBuffer() vp_override { return m_colorBuffer; } + bool isShowingSpeculars() const { return m_showSpeculars; } + protected: void setupScene() vp_override; void setupRenderTarget() vp_override; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 154b0fbc47..8342e71f62 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -229,7 +229,11 @@ vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) pandaPos /= pandaPos[3]; return pandaPos; } - +vpColVector vpPanda3DBaseRenderer::vispVectorToPanda(const vpColVector &point) +{ + vpColVector pandaPos = PANDA_T_VISP.getRotationMatrix() * point; + return pandaPos; +} diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp index 4ddd5a933b..8e616b4944 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -78,6 +78,7 @@ void vpPanda3DPostProcessFilter::setupRenderTarget() m_buffers.push_back(m_buffer); m_buffer->set_inverted(gsg->get_copy_texture_inverted()); m_texture = new Texture(); + fbp.setup_color_texture(m_texture); m_buffer->add_render_texture(m_texture, m_isOutput ? GraphicsOutput::RenderTextureMode::RTM_copy_ram : GraphicsOutput::RenderTextureMode::RTM_copy_texture); m_buffer->set_clear_color(LColor(0.f)); m_buffer->set_clear_color_active(true); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 4135a7e6af..e6e34aeef8 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -209,7 +209,6 @@ std::string vpPanda3DRGBRenderer::makeFragmentShader(bool hasTexture, bool specu ss << "#undef SPECULAR" << std::endl; } ss << vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG; - std::cout << ss.str() << std::endl; return ss.str(); } @@ -217,9 +216,18 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); - const RenderAttrib *textureAttrib = objectInScene.get_attrib(TextureAttrib::get_class_type()); - bool hasTexture = textureAttrib != nullptr; - std::cout << "SHOW SPECULARS = " << m_showSpeculars << std::endl; + TextureCollection txs = objectInScene.find_all_textures(); + bool hasTexture = txs.size() > 0; + // gltf2bam and other tools may store some fallback textures. We shouldnt use them as they whiten the result + if (hasTexture) { + std::vector fallbackNames { "pbr-fallback", "normal-fallback", "emission-fallback" }; + unsigned numMatches = 0; + for (const std::string &fallbackName: fallbackNames) { + numMatches += static_cast(txs.find_texture(fallbackName) != nullptr); + } + hasTexture = txs.size() > numMatches; // Some textures are not fallback textures + } + PT(Shader) shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, COOK_TORRANCE_VERT, makeFragmentShader(hasTexture, m_showSpeculars)); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 23439ecd60..843fe813f9 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,6 +1,39 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + //! \example tutorial-panda3d-renderer.cpp + #include #include + #if defined(VISP_HAVE_PANDA3D) && defined(VISP_HAVE_DISPLAY) && defined(VISP_HAVE_MODULE_IO) #include @@ -62,11 +95,21 @@ void displayCanny(const vpImage &cannyRawData, #pragma omp parallel for for (int i = 0; i < cannyRawData.getSize(); ++i) { vpRGBf &px = cannyRawData.bitmap[i]; - //canny.bitmap[i] = 255 * (px.R * px.R + px.G * px.G > 0); - canny.bitmap[i] = static_cast(127.5f + 127.5f * atan(px.B)); + canny.bitmap[i] = 255 * (px.R * px.R + px.G * px.G > 0); + //canny.bitmap[i] = static_cast(127.5f + 127.5f * atan(px.B)); } vpDisplay::display(canny); + for (unsigned int i = 0; i < canny.getHeight(); i += 8) { + for (unsigned int j = 0; j < canny.getWidth(); j += 8) { + bool valid = (pow(cannyRawData[i][j].R, 2.f) + pow(cannyRawData[i][j].G, 2.f)) > 0; + if (!valid) continue; + float angle = cannyRawData[i][j].B; + unsigned x = j + 10 * cos(angle); + unsigned y = i + 10 * sin(angle); + vpDisplay::displayArrow(canny, i, j, y, x, vpColor::green); + } + } vpDisplay::flush(canny); } @@ -124,7 +167,7 @@ int main(int argc, const char **argv) std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); - std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 20.f); + std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 8.f); renderer.addSubRenderer(geometryRenderer); @@ -159,9 +202,11 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); - renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.4, -0.1 }), vpColVector({ 0.0, 0.0, 1.0 })); + // renderer.addLight(alight); + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.2, 0.2, -0.2 }), vpColVector({ 0.0, 0.0, 2.0 })); renderer.addLight(plight); + vpPanda3DDirectionalLight dlight("Directional", vpRGBf(2.0), vpColVector({ 0.0, -1.0, 0.0 })); + //renderer.addLight(dlight); rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; From 2971eb2f448471ec052140cdaa76d37e1d8a39f2 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 3 May 2024 17:18:07 +0200 Subject: [PATCH 55/65] fix texture inversion --- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 2 - .../visp3/ar/vpPanda3DGeometryRenderer.h | 2 +- .../visp3/ar/vpPanda3DPostProcessFilter.h | 4 + .../vpPanda3DBaseRenderer.cpp | 10 -- .../vpPanda3DCommonFilters.cpp | 42 +------- .../vpPanda3DGeometryRenderer.cpp | 96 +++++++++---------- .../vpPanda3DPostProcessFilter.cpp | 52 +++++++++- .../vpPanda3DRGBRenderer.cpp | 27 ++++-- tutorial/ar/tutorial-panda3d-renderer.cpp | 9 +- 9 files changed, 127 insertions(+), 117 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index 649469def4..cceaafcfc7 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -243,13 +243,11 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @param abort whether to abort (true) or display a message when an assertion fails. */ void setAbortOnPandaError(bool abort); - void setForcedInvertTextures(bool invert); void enableDebugLog(); static vpColVector vispPointToPanda(const vpColVector &point); static vpColVector vispVectorToPanda(const vpColVector &vec); - void printStructure(); virtual GraphicsOutput *getMainOutputBuffer() { return nullptr; } diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 9b7b4c5544..3bc3471089 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -40,7 +40,7 @@ #include /** - * \ingroup group_ar_renderer_panda3d + * \ingroup group_ar_renderer_panda3d_3d * * @brief Renderer that outputs object geometric information. * diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h index dfe8a424e4..494b41c830 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -48,6 +48,10 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer void setRenderParameters(const vpPanda3DRenderParameters ¶ms) vp_override; + void getRenderBasic(vpImage &I) const; + void getRenderBasic(vpImage &I) const; + + virtual FrameBufferProperties getBufferProperties() const = 0; std::shared_ptr m_inputRenderer; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 8342e71f62..7aa31e736c 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -204,16 +204,6 @@ void vpPanda3DBaseRenderer::setAbortOnPandaError(bool abort) } } -void vpPanda3DBaseRenderer::setForcedInvertTextures(bool invert) -{ - if (invert) { - load_prc_file_data("", "copy-texture-inverted 1"); - } - else { - load_prc_file_data("", "copy-texture-inverted 0"); - } -} - void vpPanda3DBaseRenderer::enableDebugLog() { load_prc_file_data("", "gl-debug 1"); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index fc56358dc3..998c848ec2 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -32,20 +32,7 @@ FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const } void vpPanda3DLuminanceFilter::getRender(vpImage &I) const { - if (!m_isOutput) { - throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); - } - unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image - I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); - unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); - if (indexMultiplier != 1) { - for (unsigned int i = 0; i < I.getSize(); ++i) { - I.bitmap[i] = data[i * indexMultiplier]; - } - } - else { - memcpy(I.bitmap, &data[0], I.getSize() * sizeof(unsigned char)); - } + vpPanda3DPostProcessFilter::getRenderBasic(I); } @@ -101,20 +88,7 @@ FrameBufferProperties vpPanda3DGaussianBlur::getBufferProperties() const void vpPanda3DGaussianBlur::getRender(vpImage &I) const { - if (!m_isOutput) { - throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); - } - unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image - I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); - unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); - if (indexMultiplier != 1) { - for (unsigned int i = 0; i < I.getSize(); ++i) { - I.bitmap[i] = data[i * indexMultiplier]; - } - } - else { - memcpy(I.bitmap, &data[0], I.getSize() * sizeof(unsigned char)); - } + vpPanda3DPostProcessFilter::getRenderBasic(I); } const char *vpPanda3DCanny::FRAGMENT_SHADER = R"shader( @@ -204,17 +178,7 @@ FrameBufferProperties vpPanda3DCanny::getBufferProperties() const void vpPanda3DCanny::getRender(vpImage &I) const { - if (!m_isOutput) { - throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); - } - unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image - I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); - float *data = (float *)(&(m_texture->get_ram_image().front())); - for (unsigned int i = 0; i < I.getSize(); ++i) { - I.bitmap[i].B = (data[i * 4]); - I.bitmap[i].G = (data[i * 4 + 1]); - I.bitmap[i].R = (data[i * 4 + 2]); - } + vpPanda3DPostProcessFilter::getRenderBasic(I); } #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index dfd77db411..426e120e13 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -187,62 +187,49 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImageget_y_size(), m_normalDepthTexture->get_x_size()); depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); - // unsigned expectedSize = m_normalDepthTexture->get_y_size() * m_normalDepthTexture->get_x_size() * sizeof(float) * 4; - // unsigned actualSize = m_normalDepthTexture->get_ram_image_size(); - // if (expectedSize != actualSize) { - // throw vpException(vpException::fatalError, "Expected %d bytes, but got %d bytes", expectedSize, actualSize); - // } - // std::stringstream ss; - // ss << "Texture info:" << std::endl; - // ss << "Has ram image: " << m_normalDepthTexture->has_ram_image() << std::endl; - // ss << "Size (H x W): " << m_normalDepthTexture->get_y_size() << " x " << m_normalDepthTexture->get_x_size() << std::endl; - // ss << "Number of channels: " << m_normalDepthTexture->get_num_components() << std::endl; - // ss << "Bytes per channel: " << m_normalDepthTexture->get_component_width() << std::endl; - // ss << "Channel type: " << m_normalDepthTexture->get_component_type() << std::endl; - // std::cout << ss.str() << std::endl; - - if (m_normalDepthTexture->get_component_type() == Texture::T_float) { - float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (data[i * 4]); - normals.bitmap[i].G = (data[i * 4 + 1]); - normals.bitmap[i].R = (data[i * 4 + 2]); - depth.bitmap[i] = (data[i * 4 + 3]); - } - + if (m_normalDepthTexture->get_component_type() != Texture::T_float) { + throw vpException(vpException::badValue, "Unexpected data type in normals texture"); } - else if (m_normalDepthTexture->get_component_type() == Texture::T_unsigned_byte) { - unsigned char *data = (unsigned char *)(&(m_normalDepthTexture->get_ram_image().front())); - std::cout << "AAAAAAA" << std::endl; - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (static_cast(data[i * 4]) / 127.5f - 1.0); - normals.bitmap[i].G = (static_cast(data[i * 4 + 1]) / 127.5f - 1.0); - normals.bitmap[i].R = (static_cast(data[i * 4 + 2]) / 127.5f - 1.0); - depth.bitmap[i] = ((static_cast(data[i * 4 + 3]))); + + int rowIncrement = normals.getWidth() * 4; + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + data = data + rowIncrement * (normals.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < normals.getHeight(); ++i) { + data += rowIncrement; + vpRGBf *normalRow = normals[i]; + float *depthRow = depth[i]; + for (unsigned int j = 0; j < normals.getWidth(); ++j) { + normalRow[j].B = (data[j * 4]); + normalRow[j].G = (data[j * 4 + 1]); + normalRow[j].R = (data[j * 4 + 2]); + depthRow[j] = (data[j * 4 + 3]); } } + //#pragma omp parallel for simd } void vpPanda3DGeometryRenderer::getRender(vpImage &normals) const { normals.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); - -//#pragma omp parallel for simd - if (m_normalDepthTexture->get_component_type() == Texture::T_float) { - float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (data[i * 4]); - normals.bitmap[i].G = (data[i * 4 + 1]); - normals.bitmap[i].R = (data[i * 4 + 2]); - } + if (m_normalDepthTexture->get_component_type() != Texture::T_float) { + throw vpException(vpException::badValue, "Unexpected data type in normals texture"); } - else if (m_normalDepthTexture->get_component_type() == Texture::T_unsigned_byte) { - unsigned char *data = (unsigned char *)(&(m_normalDepthTexture->get_ram_image().front())); - for (unsigned int i = 0; i < normals.getSize(); ++i) { - normals.bitmap[i].B = (static_cast(data[i * 4]) / 127.5f - 1.0); - normals.bitmap[i].G = (static_cast(data[i * 4 + 1]) / 127.5f - 1.0); - normals.bitmap[i].R = (static_cast(data[i * 4 + 2]) / 127.5f - 1.0); + + int rowIncrement = normals.getWidth() * 4; + float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); + data = data + rowIncrement * (normals.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < normals.getHeight(); ++i) { + data += rowIncrement; + vpRGBf *normalRow = normals[i]; + for (unsigned int j = 0; j < normals.getWidth(); ++j) { + normalRow[j].B = (data[j * 4]); + normalRow[j].G = (data[j * 4 + 1]); + normalRow[j].R = (data[j * 4 + 2]); } } } @@ -250,10 +237,21 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals) const void vpPanda3DGeometryRenderer::getRender(vpImage &depth) const { depth.resize(m_normalDepthTexture->get_y_size(), m_normalDepthTexture->get_x_size()); + if (m_normalDepthTexture->get_component_type() != Texture::T_float) { + throw vpException(vpException::badValue, "Unexpected data type in normals texture"); + } + + int rowIncrement = depth.getWidth() * 4; float *data = (float *)(&(m_normalDepthTexture->get_ram_image().front())); -//#pragma omp parallel for simd - for (unsigned int i = 0; i < depth.getSize(); ++i) { - depth.bitmap[i] = (data[i * 4 + 3]); + data = data + rowIncrement * (depth.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < depth.getHeight(); ++i) { + data += rowIncrement; + float *depthRow = depth[i]; + for (unsigned int j = 0; j < depth.getWidth(); ++j) { + depthRow[j] = (data[j * 4 + 3]); + } } } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp index 8e616b4944..35e2251ba5 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -76,7 +76,7 @@ void vpPanda3DPostProcessFilter::setupRenderTarget() throw vpException(vpException::fatalError, "Could not create buffer"); } m_buffers.push_back(m_buffer); - m_buffer->set_inverted(gsg->get_copy_texture_inverted()); + //m_buffer->set_inverted(true); m_texture = new Texture(); fbp.setup_color_texture(m_texture); m_buffer->add_render_texture(m_texture, m_isOutput ? GraphicsOutput::RenderTextureMode::RTM_copy_ram : GraphicsOutput::RenderTextureMode::RTM_copy_texture); @@ -115,4 +115,54 @@ void vpPanda3DPostProcessFilter::setRenderParameters(const vpPanda3DRenderParame } } +void vpPanda3DPostProcessFilter::getRenderBasic(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + const unsigned numComponents = m_texture->get_num_components(); + int rowIncrement = I.getWidth() * numComponents; // we ask for only 8 bits image, but we may get an rgb image + unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); + // Panda3D stores data upside down + data += rowIncrement * (I.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < I.getHeight(); ++i) { + data += rowIncrement; + unsigned char *colorRow = I[i]; + for (unsigned int j = 0; j < I.getWidth(); ++j) { + colorRow[j] = data[j * numComponents]; + } + } +} + +void vpPanda3DPostProcessFilter::getRenderBasic(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + const unsigned numComponents = m_texture->get_num_components(); + int rowIncrement = I.getWidth() * numComponents; // we ask for only 8 bits image, but we may get an rgb image + float *data = (float *)(&(m_texture->get_ram_image().front())); + // Panda3D stores data upside down + data += rowIncrement * (I.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < I.getHeight(); ++i) { + data += rowIncrement; + vpRGBf *colorRow = I[i]; + for (unsigned int j = 0; j < I.getWidth(); ++j) { + colorRow[j].B = data[j * numComponents]; + colorRow[j].G = data[j * numComponents + 1]; + colorRow[j].R = data[j * numComponents + 2]; + + } + } +} + + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index e6e34aeef8..b1171a833b 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -242,14 +242,25 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); unsigned char *data = (unsigned char *)(&(m_colorTexture->get_ram_image().front())); - // BGRA order in panda3d - for (unsigned int i = 0; i < I.getSize(); ++i) { - I.bitmap[i].B = data[i * 4]; - I.bitmap[i].G = data[i * 4 + 1]; - I.bitmap[i].R = data[i * 4 + 2]; - I.bitmap[i].A = data[i * 4 + 3]; + int rowIncrement = I.getWidth() * 4; + // Panda3D stores the image using the OpenGL convention (origin is bottom left), + // while we store data with origin as upper left. We copy with a flip + data = data + rowIncrement * (I.getHeight() - 1); + rowIncrement = -rowIncrement; + + for (unsigned int i = 0; i < I.getHeight(); ++i) { + data += rowIncrement; + vpRGBa *colorRow = I[i]; + for (unsigned int j = 0; j < I.getWidth(); ++j) { + // BGRA order in panda3d + colorRow[j].B = data[j * 4]; + colorRow[j].G = data[j * 4 + 1]; + colorRow[j].R = data[j * 4 + 2]; + colorRow[j].A = data[j * 4 + 3]; + } } - // memcpy(I.bitmap, data, sizeof(unsigned char) * I.getSize() * 4); + + } void vpPanda3DRGBRenderer::setupScene() @@ -288,7 +299,7 @@ void vpPanda3DRGBRenderer::setupRenderTarget() throw vpException(vpException::fatalError, "Could not create color buffer"); } m_buffers.push_back(m_colorBuffer); - m_colorBuffer->set_inverted(gsg->get_copy_texture_inverted()); + //m_colorBuffer->set_inverted(gsg->get_copy_texture_inverted()); m_colorTexture = new Texture(); fbp.setup_color_texture(m_colorTexture); //m_colorTexture->set_format(Texture::Format::F_srgb_alpha); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 843fe813f9..e15b7b2ff8 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -115,7 +115,6 @@ void displayCanny(const vpImage &cannyRawData, int main(int argc, const char **argv) { - bool invertTexture = false; bool stepByStep = false; bool debug = false; bool showLightContrib = false; @@ -123,8 +122,6 @@ int main(int argc, const char **argv) char *modelPathCstr = nullptr; vpParseArgv::vpArgvInfo argTable[] = { - {"-invert", vpParseArgv::ARGV_CONSTANT_BOOL, 0, (char *)&invertTexture, - "Whether to force Texture inversion. Use this if the model is upside down."}, {"-model", vpParseArgv::ARGV_STRING, (char *) nullptr, (char *)&modelPathCstr, "Path to the model to load."}, {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, @@ -167,7 +164,7 @@ int main(int argc, const char **argv) std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); - std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 8.f); + std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 10.f); renderer.addSubRenderer(geometryRenderer); @@ -188,9 +185,7 @@ int main(int argc, const char **argv) if (debug) { renderer.enableDebugLog(); } - if (invertTexture) { - renderer.setForcedInvertTextures(true); - } + std::cout << "Initializing Panda3D rendering framework" << std::endl; renderer.initFramework(); From 5f6db926d21ab083b0a302366a81f6701c000d39 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 3 May 2024 17:50:59 +0200 Subject: [PATCH 56/65] change camera normal frame to be more coherent, update tutorial model --- .../visp3/ar/vpPanda3DGeometryRenderer.h | 4 +-- .../vpPanda3DGeometryRenderer.cpp | 4 +-- tutorial/ar/data/deformed_sphere.bam | Bin 56235 -> 0 bytes tutorial/ar/data/suzanne.bam | Bin 0 -> 95578 bytes tutorial/ar/tutorial-panda3d-renderer.cpp | 34 +++++++++++------- 5 files changed, 25 insertions(+), 17 deletions(-) delete mode 100644 tutorial/ar/data/deformed_sphere.bam create mode 100644 tutorial/ar/data/suzanne.bam diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 3bc3471089..358dee8697 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -46,7 +46,7 @@ * * This information may contain, depending on requested render type: * - * - Normals in the world frame or in the camera frame + * - Normals in the world frame or in the camera frame. * - Depth information */ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer @@ -56,7 +56,7 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer enum vpRenderType { WORLD_NORMALS, //! Surface normals in world space. - CAMERA_NORMALS, //! Surface normals in the frame of the camera + CAMERA_NORMALS, //! Surface normals in the frame of the camera. Z points towards the camera and y is up. }; vpPanda3DGeometryRenderer(vpRenderType renderType); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 426e120e13..db506c402f 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -56,8 +56,8 @@ void main() gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; // View space is Z-up right handed, flip z and y oNormal = p3d_NormalMatrix * normalize(p3d_Normal); - oNormal.yz = oNormal.zy; - oNormal.y = -oNormal.y; + // oNormal.yz = oNormal.zy; + // oNormal.y = -oNormal.y; vec4 cs_position = p3d_ModelViewMatrix * p3d_Vertex; distToCamera = -cs_position.z; } diff --git a/tutorial/ar/data/deformed_sphere.bam b/tutorial/ar/data/deformed_sphere.bam deleted file mode 100644 index 877858f8b6a1dcd32b70fee6b7cde1146f110ec4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56235 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 VmQVT1Y}n<#kv)5jf>?v`{{hni94r6; literal 0 HcmV?d00001 diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index e15b7b2ff8..c3469a2dfb 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -130,7 +130,6 @@ int main(int argc, const char **argv) "Show frames step by step."}, {"-canny", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&showCanny, "Show frames step by step."}, - {"-debug", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&debug, "Show Opengl/Panda3D debug message."}, {"-h", vpParseArgv::ARGV_HELP, (char *) nullptr, (char *) nullptr, @@ -150,7 +149,7 @@ int main(int argc, const char **argv) modelPath = modelPathCstr; } else { - modelPath = "data/deformed_sphere.bam"; + modelPath = "data/suzanne.bam"; } vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); @@ -158,13 +157,20 @@ int main(int argc, const char **argv) const std::string objectName = "object"; - std::shared_ptr geometryRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS); - std::shared_ptr cameraRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); - std::shared_ptr rgbRenderer = std::make_shared(); - std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); - std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); - std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); - std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 10.f); + std::shared_ptr geometryRenderer = + std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS); + std::shared_ptr cameraRenderer = + std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); + std::shared_ptr rgbRenderer = + std::make_shared(); + std::shared_ptr rgbDiffuseRenderer = + std::make_shared(false); + std::shared_ptr grayscaleFilter = + std::make_shared("toGrayscale", rgbRenderer, false); + std::shared_ptr blurFilter = + std::make_shared("blur", grayscaleFilter, false); + std::shared_ptr cannyFilter = + std::make_shared("canny", blurFilter, true, 10.f); renderer.addSubRenderer(geometryRenderer); @@ -197,11 +203,13 @@ int main(int argc, const char **argv) renderer.addNodeToScene(object); vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); - // renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.2, 0.2, -0.2 }), vpColVector({ 0.0, 0.0, 2.0 })); + renderer.addLight(alight); + + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.3, -0.4, -0.2 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); - vpPanda3DDirectionalLight dlight("Directional", vpRGBf(2.0), vpColVector({ 0.0, -1.0, 0.0 })); - //renderer.addLight(dlight); + + vpPanda3DDirectionalLight dlight("Directional", vpRGBf(2.0), vpColVector({ 1.0, 1.0, 0.0 })); + renderer.addLight(dlight); rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; From 626ed6aeaa90f4d37fe26e329a9058ff7696cf6c Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 3 May 2024 18:02:16 +0200 Subject: [PATCH 57/65] setup tutorial --- doc/tutorial/rendering/tutorial-panda3d.dox | 6 ++++++ doc/tutorial/tutorial-users.dox | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 doc/tutorial/rendering/tutorial-panda3d.dox diff --git a/doc/tutorial/rendering/tutorial-panda3d.dox b/doc/tutorial/rendering/tutorial-panda3d.dox new file mode 100644 index 0000000000..9ee001aa6c --- /dev/null +++ b/doc/tutorial/rendering/tutorial-panda3d.dox @@ -0,0 +1,6 @@ +/** +\page tutorial-panda3d Rendering a 3D scene with Panda3D +\tableofcontents + +\section tutorial-panda3d-intro Introduction +*/ diff --git a/doc/tutorial/tutorial-users.dox b/doc/tutorial/tutorial-users.dox index e0d08a9370..25ffd37c61 100644 --- a/doc/tutorial/tutorial-users.dox +++ b/doc/tutorial/tutorial-users.dox @@ -11,6 +11,8 @@ This page references all the tutorials to use and contribute to ViSP. - \subpage tutorial_calib +- \subpage tutorial_rendering + - \subpage tutorial_tracking - \subpage tutorial_detection @@ -85,6 +87,12 @@ This page introduces the user to the way to calibrate a camera. - \subpage tutorial-calibration-extrinsic
This tutorial explains how to get the camera eye-in-hand extrinsic transformation when the camera is mounted on a robot end-effector. */ +/*! \page tutorial_rendering 3D rendering and augmented reality +This page gives an introduction to using the 3D rendering tools integrated into ViSP. + +- \subpage tutorial-panda3d
This tutorial gives an overview of the rendering capabilities of the Panda3D wrapper. +*/ + /*! \page tutorial_tracking Tracking This page introduces the user to the way to track objects in images. From a96124987697dbcfe58edaac424c9369824cd1c2 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 24 May 2024 16:31:02 +0200 Subject: [PATCH 58/65] Added ability to set backgorund image for RGB renderer --- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 8 ++- .../vpPanda3DGeometryRenderer.cpp | 8 ++- .../vpPanda3DRGBRenderer.cpp | 54 ++++++++++++++++++- tutorial/ar/tutorial-panda3d-renderer.cpp | 30 ++++++----- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index f2402658b6..2fd89dba4a 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -68,7 +68,7 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp * \brief Default constructor. Initialize an RGB renderer with the normal rendering behavior showing speculars * */ - vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB"), m_showSpeculars(true) { } + vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB"), m_showSpeculars(true), m_display2d(nullptr), m_backgroundTexture(nullptr) { } /** * \brief RGB renderer constructor allowing to specify * whether specular highlights should be rendered or @@ -90,10 +90,13 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp void addNodeToScene(const NodePath &object) vp_override; + void setBackgroundImage(const vpImage &background); + GraphicsOutput *getMainOutputBuffer() vp_override { return m_colorBuffer; } bool isShowingSpeculars() const { return m_showSpeculars; } + protected: void setupScene() vp_override; void setupRenderTarget() vp_override; @@ -106,6 +109,9 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp static const char *COOK_TORRANCE_VERT; static const char *COOK_TORRANCE_FRAG; + NodePath m_backgroundImage; + DisplayRegion *m_display2d; + PT(Texture) m_backgroundTexture; }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index db506c402f..962e3f1bd5 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -197,7 +197,6 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals, vpImage &normals, vpImage &normals) const @@ -224,13 +222,13 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &normals) const rowIncrement = -rowIncrement; for (unsigned int i = 0; i < normals.getHeight(); ++i) { - data += rowIncrement; vpRGBf *normalRow = normals[i]; for (unsigned int j = 0; j < normals.getWidth(); ++j) { normalRow[j].B = (data[j * 4]); normalRow[j].G = (data[j * 4 + 1]); normalRow[j].R = (data[j * 4 + 2]); } + data += rowIncrement; } } @@ -247,11 +245,11 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &depth) const rowIncrement = -rowIncrement; for (unsigned int i = 0; i < depth.getHeight(); ++i) { - data += rowIncrement; float *depthRow = depth[i]; for (unsigned int j = 0; j < depth.getWidth(); ++j) { depthRow[j] = (data[j * 4 + 3]); } + data += rowIncrement; } } diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index b1171a833b..93f6bc586e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -33,6 +33,10 @@ #if defined(VISP_HAVE_PANDA3D) +#include "orthographicLens.h" +#include "cardMaker.h" +#include "texturePool.h" + const char *vpPanda3DRGBRenderer::COOK_TORRANCE_VERT = R"shader( #version 330 @@ -238,6 +242,54 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) } +void vpPanda3DRGBRenderer::setBackgroundImage(const vpImage &background) +{ + + if (m_display2d == nullptr) { + CardMaker cm("card"); + cm.set_frame_fullscreen_quad(); + + NodePath myCamera2d(new Camera("myCam2d")); + PT(OrthographicLens) lens = new OrthographicLens(); + lens->set_film_size(2, 2); + lens->set_near_far(-1000, 1000); + lens->set_film_offset(0, 0); + ((Camera *)myCamera2d.node())->set_lens(lens); + + NodePath myRender2d("myRender2d"); + myRender2d.set_depth_test(false); + myRender2d.set_depth_write(false); + myCamera2d.reparent_to(myRender2d); + m_backgroundImage = myRender2d.attach_new_node(cm.generate()); + + m_display2d = m_colorBuffer->make_display_region(); + m_display2d->set_sort(-100); + m_display2d->set_camera(myCamera2d); + } + if (m_backgroundTexture == nullptr) { + m_backgroundTexture = new Texture(); + } + m_backgroundImage.set_texture(m_backgroundTexture); + m_backgroundTexture->setup_2d_texture(background.getWidth(), background.getHeight(), + Texture::ComponentType::T_unsigned_byte, + Texture::Format::F_rgba8); + //m_backgroundTexture = TexturePool::load_texture("/home/sfelton/IMG_20230221_165330430.jpg"); + unsigned char *data = (unsigned char *)m_backgroundTexture->modify_ram_image(); + + std::cout << m_backgroundTexture->get_x_size() << ", " << m_backgroundTexture->get_y_size() << std::endl; + for (unsigned int i = 0; i < background.getHeight(); ++i) { + const vpRGBa *srcRow = background[background.getHeight() - (i + 1)]; + unsigned char *destRow = data + i * background.getWidth() * 4; + for (unsigned int j = 0; j < background.getWidth(); ++j) { + destRow[j * 4] = srcRow[j].B; + destRow[j * 4 + 1] = srcRow[j].G; + destRow[j * 4 + 2] = srcRow[j].R; + destRow[j * 4 + 3] = srcRow[j].A; + } + } + +} + void vpPanda3DRGBRenderer::getRender(vpImage &I) const { I.resize(m_colorTexture->get_y_size(), m_colorTexture->get_x_size()); @@ -249,7 +301,6 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const rowIncrement = -rowIncrement; for (unsigned int i = 0; i < I.getHeight(); ++i) { - data += rowIncrement; vpRGBa *colorRow = I[i]; for (unsigned int j = 0; j < I.getWidth(); ++j) { // BGRA order in panda3d @@ -258,6 +309,7 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const colorRow[j].R = data[j * 4 + 2]; colorRow[j].A = data[j * 4 + 3]; } + data += rowIncrement; } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index c3469a2dfb..fcd4cdb1bb 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -120,10 +121,14 @@ int main(int argc, const char **argv) bool showLightContrib = false; bool showCanny = false; char *modelPathCstr = nullptr; + char *backgroundPathCstr = nullptr; + vpParseArgv::vpArgvInfo argTable[] = { {"-model", vpParseArgv::ARGV_STRING, (char *) nullptr, (char *)&modelPathCstr, "Path to the model to load."}, + {"-background", vpParseArgv::ARGV_STRING, (char *) nullptr, (char *)&backgroundPathCstr, + "Path to the background image to load for the rgb renderer."}, {"-step", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&stepByStep, "Show frames step by step."}, {"-specular", vpParseArgv::ARGV_CONSTANT_BOOL, (char *) nullptr, (char *)&showLightContrib, @@ -151,6 +156,10 @@ int main(int argc, const char **argv) else { modelPath = "data/suzanne.bam"; } + std::string backgroundPath; + if (backgroundPathCstr) { + backgroundPath = backgroundPathCstr; + } vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); @@ -212,11 +221,17 @@ int main(int argc, const char **argv) renderer.addLight(dlight); rgbRenderer->printStructure(); + if (!backgroundPath.empty()) { + vpImage background; + vpImageIo::read(background, backgroundPath); + rgbRenderer->setBackgroundImage(background); + } + + std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); - unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); - + std::cout << "Creating display and data images" << std::endl; vpImage normalsImage; vpImage cameraNormalsImage; vpImage cannyRawData; @@ -281,7 +296,6 @@ int main(int argc, const char **argv) } const double beforeConvert = vpTime::measureTimeMs(); - displayNormals(normalsImage, normalDisplayImage); displayNormals(cameraNormalsImage, cameraNormalDisplayImage); displayDepth(depthImage, depthDisplayImage, nearV, farV); @@ -316,16 +330,6 @@ int main(int argc, const char **argv) } } } - // if (firstFrame) { - // renderParams.setImageResolution(h * 0.5, w * 0.5); - // vpCameraParameters orig = renderParams.getCameraIntrinsics(); - // vpCameraParameters newCam(orig.get_px() * 0.5, orig.get_py() * 0.5, orig.get_u0() * 0.5, orig.get_v0() * 0.5); - // renderParams.setCameraIntrinsics(newCam); - // std::cout << h << std::endl; - // //dDepth.setDownScalingFactor(0.5); - // renderer.setRenderParameters(renderParams); - // } - // firstFrame = false; const double afterAll = vpTime::measureTimeMs(); const double delta = (afterAll - beforeRender) / 1000.0; const vpHomogeneousMatrix wTo = renderer.getNodePose(objectName); From 5a143d349fa2f0fd62868bdf354fe1e64dc7920d Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 24 May 2024 17:07:09 +0200 Subject: [PATCH 59/65] Rename WORLD_NORMALS to OBJECT_NORMALS --- modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h | 2 +- .../ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp | 4 ++-- tutorial/ar/tutorial-panda3d-renderer.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 358dee8697..94ddeea2ca 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -55,7 +55,7 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer enum vpRenderType { - WORLD_NORMALS, //! Surface normals in world space. + OBJECT_NORMALS, //! Surface normals in the object frame. CAMERA_NORMALS, //! Surface normals in the frame of the camera. Z points towards the camera and y is up. }; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index 962e3f1bd5..f431244c09 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -110,7 +110,7 @@ void main() std::string renderTypeToName(vpPanda3DGeometryRenderer::vpRenderType type) { switch (type) { - case vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS: + case vpPanda3DGeometryRenderer::vpRenderType::OBJECT_NORMALS: return "normals-world"; case vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS: return "normals-camera"; @@ -125,7 +125,7 @@ void vpPanda3DGeometryRenderer::setupScene() { m_renderRoot = m_window->get_render().attach_new_node(m_name); PT(Shader) shader; - if (m_renderType == WORLD_NORMALS) { + if (m_renderType == OBJECT_NORMALS) { shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, SHADER_VERT_NORMAL_AND_DEPTH_WORLD, SHADER_FRAG_NORMAL_AND_DEPTH); diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index fcd4cdb1bb..8f5f7f601b 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -167,7 +167,7 @@ int main(int argc, const char **argv) const std::string objectName = "object"; std::shared_ptr geometryRenderer = - std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS); + std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::OBJECT_NORMALS); std::shared_ptr cameraRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); std::shared_ptr rgbRenderer = From a6ff73a71ad18a5712f54f54ddc2bddfd6d17137 Mon Sep 17 00:00:00 2001 From: Samuel Felton Date: Fri, 24 May 2024 18:44:22 +0200 Subject: [PATCH 60/65] resolve conflict --- doc/tutorial/rendering/tutorial-panda3d.dox | 19 ++++++++++ tutorial/ar/tutorial-panda3d-renderer.cpp | 41 ++++++++++++--------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/doc/tutorial/rendering/tutorial-panda3d.dox b/doc/tutorial/rendering/tutorial-panda3d.dox index 9ee001aa6c..7a6ada5a62 100644 --- a/doc/tutorial/rendering/tutorial-panda3d.dox +++ b/doc/tutorial/rendering/tutorial-panda3d.dox @@ -3,4 +3,23 @@ \tableofcontents \section tutorial-panda3d-intro Introduction + +\snippet tutorial-panda3d-renderer.cpp Renderer set + +\snippet tutorial-panda3d-renderer.cpp Subrenderers + +\snippet tutorial-panda3d-renderer.cpp Adding subrenderers + +\warning ADD plot + +\snippet tutorial-panda3d-renderer.cpp Scene configuration + + +\section tutorial-panda3d-full-code Full code + + + + +\include tutorial-panda3d-renderer.cpp + */ diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 8f5f7f601b..ffa71f4cf2 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -160,12 +160,20 @@ int main(int argc, const char **argv) if (backgroundPathCstr) { backgroundPath = backgroundPathCstr; } + const std::string objectName = "object"; + + //! [Renderer set] vpPanda3DRenderParameters renderParams(vpCameraParameters(300, 300, 160, 120), 240, 320, 0.01, 10.0); vpPanda3DRendererSet renderer(renderParams); renderer.setRenderParameters(renderParams); + renderer.setVerticalSyncEnabled(false); + renderer.setAbortOnPandaError(true); + if (debug) { + renderer.enableDebugLog(); + } + //! [Renderer set] - const std::string objectName = "object"; - + //! [Subrenderers init] std::shared_ptr geometryRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::OBJECT_NORMALS); std::shared_ptr cameraRenderer = @@ -180,8 +188,9 @@ int main(int argc, const char **argv) std::make_shared("blur", grayscaleFilter, false); std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 10.f); + //! [Subrenderers] - + //! [Adding subrenderers] renderer.addSubRenderer(geometryRenderer); renderer.addSubRenderer(cameraRenderer); renderer.addSubRenderer(rgbRenderer); @@ -192,23 +201,13 @@ int main(int argc, const char **argv) renderer.addSubRenderer(grayscaleFilter); renderer.addSubRenderer(blurFilter); renderer.addSubRenderer(cannyFilter); - - } - - renderer.setVerticalSyncEnabled(false); - renderer.setAbortOnPandaError(true); - if (debug) { - renderer.enableDebugLog(); } - - std::cout << "Initializing Panda3D rendering framework" << std::endl; renderer.initFramework(); + //! [Adding subrenderers] - std::cout << "Loading object " << modelPath << std::endl; + //! [Scene configuration] NodePath object = renderer.loadObject(objectName, modelPath); - std::cout << "Adding node to scene" <printStructure(); + <<<<<<< HEAD + rgbRenderer->printStructure(); if (!backgroundPath.empty()) { vpImage background; vpImageIo::read(background, backgroundPath); @@ -230,7 +230,14 @@ int main(int argc, const char **argv) std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); - unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); + ====== = + renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); + //! [Scene configuration] + + rgbRenderer->printStructure(); + + >>>>>>> c386b93ae(tutorial structure) + unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); std::cout << "Creating display and data images" << std::endl; vpImage normalsImage; vpImage cameraNormalsImage; From 2468231aff6b27b5dcd2d43520ab93a5069857cb Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 11 Jun 2024 11:54:40 +0200 Subject: [PATCH 61/65] Improve Panda3D based rendered doxygen tutorial --- .../renderer/img-cube-rendering-panda3d.png | Bin 0 -> 22401 bytes doc/tutorial/rendering/tutorial-panda3d.dox | 254 +++++++++++++++++- 2 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 doc/image/tutorial/renderer/img-cube-rendering-panda3d.png diff --git a/doc/image/tutorial/renderer/img-cube-rendering-panda3d.png b/doc/image/tutorial/renderer/img-cube-rendering-panda3d.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f5881e1adc3c1888ac67ce6796198fca444108 GIT binary patch literal 22401 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 XT5*BmM*UYof)SY8>fp;c^ML;W-P*#O literal 0 HcmV?d00001 diff --git a/doc/tutorial/rendering/tutorial-panda3d.dox b/doc/tutorial/rendering/tutorial-panda3d.dox index 7a6ada5a62..73787fa70a 100644 --- a/doc/tutorial/rendering/tutorial-panda3d.dox +++ b/doc/tutorial/rendering/tutorial-panda3d.dox @@ -4,22 +4,264 @@ \section tutorial-panda3d-intro Introduction -\snippet tutorial-panda3d-renderer.cpp Renderer set +In the context of providing a render-based tracker, this tutorial introduces a new, easy to use renderer based on +Panda3D. -\snippet tutorial-panda3d-renderer.cpp Subrenderers +This renderer can output: +- A color image, with support for textures and lighting +- Depth image +- Normal maps + - In world space + - In camera space -\snippet tutorial-panda3d-renderer.cpp Adding subrenderers +It only supports camera models with no distortion. -\warning ADD plot +It is also possible to compute camera clipping values, depending on the pose of an object in the camera frame. +This ensures that the depth buffer is as accurate as possible when considering this object. -\snippet tutorial-panda3d-renderer.cpp Scene configuration +Below is a set of renders for a textured cube object. +\image html img-cube-rendering-panda3d.png + +Multi-output rendering is performed via the vpPanda3DRendererSet class, which duplicates the scene across multiple +renders and synchronizes changes to objects and the camera. Each Sub renderer implements a specific type of render: +geometric (vpPanda3DGeometryRenderer) or color-based (vpPanda3DRGBRenderer) etc. They all inherit from +vpPanda3DBaseRenderer, which implements basic functions for a panda renderer. + +\section tutorial-panda3d-install Panda3D installation +\subsection tutorial-panda3d-install-ubuntu Installation on Ubuntu + +- Installer are available for Ubuntu browsing the [download](https://www.panda3d.org/download/) page. + +- Hereafter you will find the instructions to build and install Panda3D from source on Ubuntu 22.04 + \code{.sh} + $ mkdir -p $VISP_WS/3rdparty/panda3d + $ cd $VISP_WS/3rdparty/panda3d + $ git clone https://github.com/panda3d/panda3d + $ cd panda3d + $ python3 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2 --no-opencv + \endcode + At this point you can either: + 1. install the produced Debian package (recommended) with + \code{.sh} + $ sudo dpkg -i panda3d1.11_1.11.0_amd64.deb + \endcode + 2. use the Panda3D libraries located in the `built` folder without installing the Debian package + `panda3d1.11_1.11.0_amd64.deb`, but in that case you need to set `LD_LIBRARY_PATH` environment var: + \code{.sh} + $ export LD_LIBRARY_PATH=$VISP_WS/3rdparty/panda3d/panda3d/built/lib:$LD_LIBRARY_PATH + \endcode + Without setting `LD_LIBRARY_PATH` you may experience the following error when running a binary that uses + Panda3D capabilities: + \code{.sh} + $ ./tutorial-panda3d-renderer + ./tutorial-panda3d-renderer: error while loading shared libraries: libp3dtoolconfig.so.1.11: cannot open shared object file: No such file or directory + \endcode + +- Now to build ViSP with Panda3D support when Debian package `panda3d1.11_1.11.0_amd64.deb` is installed as described + in option (1), you may notice that there is nothing specific to do, just run `cmake` as usual: + \code{.sh} + $ cd $VISP_WS/visp-build + $ cmake ../visp + $ make -j$(nproc) + \endcode + +- There is also the possibility to build ViSP with Panda3D support without installing Debian package + `panda3d1.11_1.11.0_amd64.deb` as described in option (2): + - By setting `Panda3D_DIR` cmake var to the Panda3D cloned folder + \code{.sh} + $ cd $VISP_WS/visp-build + $ cmake ../visp -DPanda3D_DIR=$VISP_WS/3rdparty/panda3d/panda3d + $ make -j$(nproc) + \endcode + - By setting `Panda3D_DIR` environment variable + \code{.sh} + $ export Panda3D_DIR=$VISP_WS/3rdparty/panda3d/panda3d + $ cd $VISP_WS/visp-build + $ cmake ../visp + $ make -j$(nproc) + \endcode + +\subsection tutorial-panda3d-install-macos Installation on macOS + +- Installer are available for macOS browsing the [download](https://www.panda3d.org/download/) page. +\note For the latest Panda3D 1.10.14 SDK there is an `Installer for macOS X 10.9+` that is only compatible with + architecture `x86_64`. If you are using a Mac M1 or M2, there is no Panda3D SDK available yet for `arm64` + architecture. The solution is to build Panda3D from source. + +- Hereafter you will find the instructions to build Panda3D from source on macOS. + + - On macOS, you will need to download a set of precompiled third-party packages in order to compile Panda3D. + Navigate to PandaED [download page](https://www.panda3d.org/download/), select the lastest SDK + (in our case SDK 1.10.14), and under ` Source Code` section, download + [Thirdparty tools for macOS](https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-mac.tar.gz) + (in our case `panda3d-1.10.14-tools-mac.tar.gz`). + - Extract third-party tools for macOS from downloaded archive + \code{.sh} + $ cd ~/Downloads + $ tar xvzf panda3d-1.10.14-tools-mac.tar.gz + \endcode + - Once done clone Panda3D: + \code{.sh} + $ mkdir -p $VISP_WS/3rdparty/panda3d + $ cd $VISP_WS/3rdparty/panda3d + $ git clone https://github.com/panda3d/panda3d + $ cd panda3d + \endcode + - Move the downloaded third-party tools in Panda3D source code folder + \code{.sh} + $ mv ~/Downloads/panda3d-1.10.14/thirdparty . + \endcode + - Build Panda3D from source + \code{.sh} + $ python3 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2 --no-opencv --no-python --threads $(sysctl -n hw.logicalcpu) + \endcode + +- At this point you can either + 1. install the produced `Panda3D-1.11.0-py3.9.dmg` file (recommended) just by double clicking on it. In the + installer window, don't forget to enable the `C++ Header Files` check box before pressing the installation button. + After that you have to set `DYLIB_LIBRARY_PATH` environment var: + \code{.sh} + $ export DYLD_LIBRARY_PATH=/Library/Developer/Panda3D/lib:$DYLD_LIBRARY_PATH + \endcode + 2. or use the Panda3D libraries located in the `built` folder without installing `.dmg` file, but in that case you + need to set `DYLIB_LIBRARY_PATH` environment var: + \code{.sh} + $ export DYLD_LIBRARY_PATH=$VISP_WS/3rdparty/panda3d/panda3d/built/lib:$DYLD_LIBRARY_PATH + \endcode + Without setting `DYLD_LIBRARY_PATH` you may experience the following error when running a binary that uses + Panda3D capabilities: + \code{.sh} + $ ./tutorial-panda3d-renderer + dyld[257]: Library not loaded: @loader_path/../lib/libpanda.1.11.dylib + \endcode -\section tutorial-panda3d-full-code Full code +- Now to build ViSP with Panda3D support when `.dmg` file `Panda3D-1.11.0-py3.9.dmg` is installed, you can just + run cmake as usual. Note that PCL is not compatible with Panda3D, that's why we disable here PCL usage + (see \ref tutorial-panda3d-issue-macOS). + \code{.sh} + $ cd $VISP_WS/visp-build + $ cmake ../visp -DUSE_PCL=OFF + $ make -j$(sysctl -n hw.logicalcpu) + \endcode +- There is also the possibility to build ViSP with Panda3D support without installing the `.dmg` file + - By setting `Panda3D_DIR` cmake var to the Panda3D cloned folder + \code{.sh} + $ cd $VISP_WS/visp-build + $ cmake ../visp -DUSE_PCL=OFF -DPanda3D_DIR=$VISP_WS/3rdparty/panda3d/panda3d + $ make -j$(sysctl -n hw.logicalcpu) + \endcode + - Or by setting `Panda3D_DIR` environment variable + \code{.sh} + $ export Panda3D_DIR=$VISP_WS/3rdparty/panda3d/panda3d + $ cd $VISP_WS/visp-build + $ cmake ../visp -DUSE_PCL=OFF + $ make -j$(sysctl -n hw.logicalcpu) + \endcode +\subsection tutorial-panda3d-install-windows Installation on Windows +- Installer are available for Windows browsing the [download](https://www.panda3d.org/download/) page. + +\section tutorial-panda3d-usage Rendere based on Panda3D usage + +An example that shows how to exploit Panda3D in ViSP to render a color image with support for textures and lighting, a +depth image, normals in world space and in camera space is given in tutorial-panda3d-renderer.cpp. + +Here you will find the code used to create the renderer: +\snippet tutorial-panda3d-renderer.cpp Renderer set + +Here you will find the code used to create the sub renderers: +\snippet tutorial-panda3d-renderer.cpp Subrenderers init + +Here you will find the code used to add the sub renderers to the main renderer: +\snippet tutorial-panda3d-renderer.cpp Adding subrenderers + +Here you will find the code used to configure the scene: +\snippet tutorial-panda3d-renderer.cpp Scene configuration + +\section tutorial-panda3d-full-code Tutorial full code + +The full code of tutorial-panda3d-renderer.cpp is given below. \include tutorial-panda3d-renderer.cpp +\section tutorial-panda3d-run Execute the tutorial + +- Once ViSP is build, you may run the tutorial by: + \code{.sh} + $ cd $VISP_WS/visp-build + $ ./tutorial/ar/tutorial-panda3d-renderer + \endcode + It downloads the object located by default in `tutorial/ar/data/suzanne.bam` file. +- You should see something similar to the following video +\htmlonly +

+\endhtmlonly + +\section tutorial-panda3d-issue Known issues +\subsection tutorial-panda3d-issue-macOS Known issue on macOS + +- Segfault: `:framework(error): Unable to create window` + ``` + % ./tutorial-panda3d-renderer + Initializing Panda3D rendering framework + Known pipe types: + CocoaGLGraphicsPipe + (all display modules loaded.) + :framework(error): Unable to create window. + zsh: segmentation fault ./tutorial-panda3d-renderer + ``` + This issue is probably due to `EIGEN_MAX_ALIGN_BYTES` and `HAVE_PNG` macro redefinition that occurs when building ViSP with Panda3D support: + ``` + $ cd visp-build + $ make + ... + [100%] Building CXX object tutorial/ar/CMakeFiles/tutorial-panda3d-renderer.dir/tutorial-panda3d-renderer.cpp.o + In file included from $VISP_WS/visp/tutorial/ar/tutorial-panda3d-renderer.cpp:17: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h:39: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h:42: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandaFramework.h:17: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandabase.h:21: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/dtoolbase.h:22: + $VISP_WS/3rdparty/panda3d/panda3d/built/include/dtool_config.h:40:9: warning: 'HAVE_PNG' macro redefined [-Wmacro-redefined] + #define HAVE_PNG 1 + ^ + /opt/homebrew/include/pcl-1.14/pcl/pcl_config.h:53:9: note: previous definition is here + #define HAVE_PNG + ^ + In file included from $VISP_WS/visp/tutorial/ar/tutorial-panda3d-renderer.cpp:17: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h:39: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h:42: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandaFramework.h:17: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandabase.h:21: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/dtoolbase.h:22: + $VISP_WS/3rdparty/panda3d/panda3d/built/include/dtool_config.h:64:9: warning: 'HAVE_ZLIB' macro redefined [-Wmacro-redefined] + #define HAVE_ZLIB 1 + ^ + /opt/homebrew/include/pcl-1.14/pcl/pcl_config.h:55:9: note: previous definition is here + #define HAVE_ZLIB + ^ + In file included from $VISP_WS/visp/tutorial/ar/tutorial-panda3d-renderer.cpp:17: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h:39: + In file included from $VISP_WS/visp/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h:42: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandaFramework.h:17: + In file included from $VISP_WS/3rdparty/panda3d/panda3d/built/include/pandabase.h:21: + $VISP_WS/3rdparty/panda3d/panda3d/built/include/dtoolbase.h:432:9: warning: 'EIGEN_MAX_ALIGN_BYTES' macro redefined [-Wmacro-redefined] + #define EIGEN_MAX_ALIGN_BYTES MEMORY_HOOK_ALIGNMENT + ^ + /opt/homebrew/include/eigen3/Eigen/src/Core/util/ConfigureVectorization.h:175:11: note: previous definition is here + #define EIGEN_MAX_ALIGN_BYTES EIGEN_IDEAL_MAX_ALIGN_BYTES + ^ + 3 warnings generated. + [100%] Linking CXX executable tutorial-panda3d-renderer + [100%] Built target tutorial-panda3d-renderer + ``` + The work around consists in disabling `PCL` usage during ViSP configuration + ``` + $ cd $VISP_WS/visp-build + $ cmake ../visp -DUSE_PCL=OFF + $ make -j$(sysctl -n hw.logicalcpu) + ``` */ From f982611ffefa58e477ba4d76de27bee5e07f520f Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 11 Jun 2024 11:55:21 +0200 Subject: [PATCH 62/65] Code indentation --- tutorial/bridge/opencv/tutorial-bridge-opencv-camera-param.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/bridge/opencv/tutorial-bridge-opencv-camera-param.cpp b/tutorial/bridge/opencv/tutorial-bridge-opencv-camera-param.cpp index 33303df158..c5fb8621e7 100644 --- a/tutorial/bridge/opencv/tutorial-bridge-opencv-camera-param.cpp +++ b/tutorial/bridge/opencv/tutorial-bridge-opencv-camera-param.cpp @@ -15,7 +15,7 @@ int main() #ifdef ENABLE_VISP_NAMESPACE using namespace VISP_NAMESPACE_NAME; #endif -//! [Set ViSP camera parameters] + //! [Set ViSP camera parameters] double u0 = 326.6; double v0 = 215.0; double px = 582.7; From a55bdbf3f98223534b97deb385f652429e43b38c Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 11 Jun 2024 12:04:31 +0200 Subject: [PATCH 63/65] Various fixes - build fix on macOS - Update copyright header - Make header protection macro upper case - Protect pragma omp with _OPENMP macro - Remove empty lines - Fix doxygen warnings --- modules/ar/CMakeLists.txt | 2 +- modules/ar/include/visp3/ar/vpAR.h | 88 +++++++++---------- modules/ar/include/visp3/ar/vpAROgre.h | 15 ++-- .../include/visp3/ar/vpPanda3DBaseRenderer.h | 15 ++-- .../include/visp3/ar/vpPanda3DCommonFilters.h | 37 +++++++- .../visp3/ar/vpPanda3DGeometryRenderer.h | 17 ++-- modules/ar/include/visp3/ar/vpPanda3DLight.h | 14 +-- .../visp3/ar/vpPanda3DPostProcessFilter.h | 40 +++++++-- .../include/visp3/ar/vpPanda3DRGBRenderer.h | 13 +-- .../visp3/ar/vpPanda3DRenderParameters.h | 13 +-- .../include/visp3/ar/vpPanda3DRendererSet.h | 18 ++-- modules/ar/include/visp3/ar/vpSimulator.h | 15 ++-- .../include/visp3/ar/vpSimulatorException.h | 6 +- modules/ar/include/visp3/ar/vpViewer.h | 12 ++- modules/ar/src/coin-simulator/vpAR.cpp | 28 +++--- modules/ar/src/coin-simulator/vpSimulator.cpp | 11 +-- modules/ar/src/coin-simulator/vpViewer.cpp | 6 +- modules/ar/src/ogre-simulator/vpAROgre.cpp | 11 +-- .../vpPanda3DBaseRenderer.cpp | 17 ++-- .../vpPanda3DCommonFilters.cpp | 39 +++++++- .../vpPanda3DGeometryRenderer.cpp | 12 ++- .../vpPanda3DPostProcessFilter.cpp | 41 ++++++++- .../vpPanda3DRGBRenderer.cpp | 18 ++-- .../vpPanda3DRenderParameters.cpp | 12 ++- .../vpPanda3DRendererSet.cpp | 14 ++- modules/ar/src/vpSimulatorException.cpp | 11 +-- tutorial/ar/tutorial-panda3d-renderer.cpp | 82 ++++++----------- 27 files changed, 345 insertions(+), 262 deletions(-) diff --git a/modules/ar/CMakeLists.txt b/modules/ar/CMakeLists.txt index 1844714daa..4185627be7 100644 --- a/modules/ar/CMakeLists.txt +++ b/modules/ar/CMakeLists.txt @@ -1,7 +1,7 @@ ############################################################################# # # ViSP, open source Visual Servoing Platform software. -# Copyright (C) 2005 - 2023 by Inria. All rights reserved. +# Copyright (C) 2005 - 2024 by Inria. All rights reserved. # # This software is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/modules/ar/include/visp3/ar/vpAR.h b/modules/ar/include/visp3/ar/vpAR.h index da0f5585c6..cb110926cd 100644 --- a/modules/ar/include/visp3/ar/vpAR.h +++ b/modules/ar/include/visp3/ar/vpAR.h @@ -1,7 +1,7 @@ -/**************************************************************************** +/* * * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,8 +31,7 @@ * Description: * Use to display an image behind the internal view of the simulator * used for augmented reality application - * -*****************************************************************************/ + */ /*! \file vpAR.h @@ -45,8 +44,8 @@ */ -#ifndef vpAR_HH -#define vpAR_HH +#ifndef VP_AR_H +#define VP_AR_H #include @@ -84,58 +83,57 @@ BEGIN_VISP_NAMESPACE The code below shows how to use the class. \code -#include -#include -#include -#include + #include + #include + #include + #include -#ifdef VISP_HAVE_COIN3D_AND_GUI -static void *mainloopfunction(void *_simu) -{ - vpAR *simu = (vpAR *)_simu ; - simu->initMainApplication() ; + #ifdef VISP_HAVE_COIN3D_AND_GUI + static void *mainloopfunction(void *_simu) + { + vpAR *simu = (vpAR *)_simu ; + simu->initMainApplication() ; - vpImage I; - vpHomogeneousMatrix cMo; + vpImage I; + vpHomogeneousMatrix cMo; - //Your code to compute the pose cMo. + //Your code to compute the pose cMo. - //Set the image to use as background. - simu->setImage(I) ; - //Set the camera position thanks to the pose cMo computed before. - simu->setCameraPosition(cMo) ; + //Set the image to use as background. + simu->setImage(I) ; + //Set the camera position thanks to the pose cMo computed before. + simu->setCameraPosition(cMo) ; - simu->closeMainApplication(); -} -#endif + simu->closeMainApplication(); + } + #endif -int main() -{ -#ifdef VISP_HAVE_COIN3D_AND_GUI - vpAR simu; - //Camera parameters. - vpCameraParameters cam(600,600,160,120); + int main() + { + #ifdef VISP_HAVE_COIN3D_AND_GUI + vpAR simu; + //Camera parameters. + vpCameraParameters cam(600,600,160,120); - //Initialize the internal view of the simulator. - simu.initInternalViewer(640,480, vpSimulator::grayImage); + //Initialize the internal view of the simulator. + simu.initInternalViewer(640,480, vpSimulator::grayImage); - vpTime::wait(300); + vpTime::wait(300); - // Load the cad model. 4points.iv can be downloaded on the website - // with the image package - simu.load("./4points.iv"); + // Load the cad model. 4points.iv can be downloaded on the website + // with the image package + simu.load("./4points.iv"); - //Initialize the internal camera parameters. - simu.setInternalCameraParameters(cam); + //Initialize the internal camera parameters. + simu.setInternalCameraParameters(cam); - simu.initApplication(&mainloopfunction); + simu.initApplication(&mainloopfunction); - simu.mainLoop(); -#endif - return 0; -} + simu.mainLoop(); + #endif + return 0; + } \endcode - */ class VISP_EXPORT vpAR : public vpSimulator { diff --git a/modules/ar/include/visp3/ar/vpAROgre.h b/modules/ar/include/visp3/ar/vpAROgre.h index 62046d5505..87c2baa8ad 100644 --- a/modules/ar/include/visp3/ar/vpAROgre.h +++ b/modules/ar/include/visp3/ar/vpAROgre.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +29,7 @@ * * Description: * Augmented Reality viewer using Ogre3D. - * - * Authors: - * Bertrand Delabarre - * -*****************************************************************************/ + */ /*! \file vpAROgre.h @@ -46,8 +41,8 @@ */ -#ifndef _vpAROgre_h_ -#define _vpAROgre_h_ +#ifndef VP_AR_OGRE_H +#define VP_AR_OGRE_H #include diff --git a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h index cceaafcfc7..bd02273272 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,8 +28,8 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPanda3DBaseRenderer_h -#define vpPanda3DBaseRenderer_h +#ifndef VP_PANDA3D_BASE_RENDERER_H +#define VP_PANDA3D_BASE_RENDERER_H #include @@ -42,6 +41,7 @@ #include #include +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d * @@ -53,7 +53,7 @@ * - setupScene: This is where you should apply your shaders. * - setupCamera: This is where cameras are created and intrinsics parameters are applied * - setupRenderTarget: This is where you should create the texture buffers, where the render results should be stored. - */ +*/ class VISP_EXPORT vpPanda3DBaseRenderer { public: @@ -69,8 +69,6 @@ class VISP_EXPORT vpPanda3DBaseRenderer * @brief Initialize the whole Panda3D framework. Create a new PandaFramework object and a new window. * * Will also perform the renderer setup (scene, camera and render targets) - * - * @param showWindow whether the created window should be visible */ virtual void initFramework(); @@ -291,5 +289,6 @@ class VISP_EXPORT vpPanda3DBaseRenderer std::vector m_buffers; //! Set of buffers that this renderer uses. This storage contains weak refs to those buffers and should not deallocate them. }; +END_VISP_NAMESPACE #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h index 647a8a2249..5b1e40016f 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h @@ -1,5 +1,35 @@ -#ifndef vpPanda3DCommonFilters_h -#define vpPanda3DCommonFilters_h +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_COMMON_FILTERS_H +#define VP_PANDA3D_COMMON_FILTERS_H #include @@ -7,6 +37,7 @@ #include +BEGIN_VISP_NAMESPACE class vpPanda3DRGBRenderer; /** @@ -69,6 +100,6 @@ class VISP_EXPORT vpPanda3DCanny : public vpPanda3DPostProcessFilter float m_edgeThreshold; }; - +END_VISP_NAMESPACE #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h index 94ddeea2ca..b15e192f8b 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,8 +28,8 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPanda3DGeometryRenderer_h -#define vpPanda3DGeometryRenderer_h +#ifndef VP_PANDA3D_GEOMETRY_RENDERER_H +#define VP_PANDA3D_GEOMETRY_RENDERER_H #include @@ -39,6 +38,7 @@ #include #include +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d_3d * @@ -48,7 +48,7 @@ * * - Normals in the world frame or in the camera frame. * - Depth information - */ +*/ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer { public: @@ -78,11 +78,10 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer void getRender(vpImage &colorData) const; /** * @brief Get render results into ViSP readable structures. This version only retrieves the depth data. - * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + * @param depth Depending on the vpRenderType, rendered depth may be stored in this image. */ void getRender(vpImage &depth) const; - GraphicsOutput *getMainOutputBuffer() vp_override { return m_normalDepthBuffer; } protected: @@ -99,6 +98,6 @@ class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer static const char *SHADER_FRAG_NORMAL_AND_DEPTH; }; - +END_VISP_NAMESPACE #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DLight.h b/modules/ar/include/visp3/ar/vpPanda3DLight.h index 3836e61257..f225a29390 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DLight.h +++ b/modules/ar/include/visp3/ar/vpPanda3DLight.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,8 +28,8 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPand3DLight_h -#define vpPand3DLight_h +#ifndef VP_PANDA3D_LIGHT_H +#define VP_PANDA3D_LIGHT_H #include @@ -47,6 +46,7 @@ #include "pointLight.h" #include "directionalLight.h" +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d_lighting * @@ -57,7 +57,7 @@ * * \see https://docs.panda3d.org/1.10/cpp/programming/render-attributes/lighting * - */ +*/ class VISP_EXPORT vpPanda3DLight { public: @@ -269,7 +269,7 @@ class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable NodePath m_lightableScene; //! Scene that should be lit when calling addLight }; - +END_VISP_NAMESPACE #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h index 494b41c830..c5986d2379 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -1,5 +1,35 @@ -#ifndef vpPanda3DPostProcessFilter_h -#define vpPanda3DPostProcessFilter_h +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_POST_PROCESS_FILTER_H +#define VP_PANDA3D_POST_PROCESS_FILTER_H #include @@ -8,7 +38,7 @@ #include "cardMaker.h" #include "orthographicLens.h" - +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d_filters * \brief Base class for postprocessing filters that map the result of a vpPanda3DBaseRenderer to a new image. @@ -22,7 +52,7 @@ * that is placed perfectly in front of the camera. * - A shader (given as an argument to the constructor) is applied to this quad. * - The result is copied back to ram if required. - */ +*/ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer { public: @@ -63,6 +93,6 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer static const char *FILTER_VERTEX_SHADER; }; - +END_VISP_NAMESPACE #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h index 2fd89dba4a..8132974df2 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,8 +28,8 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPanda3DRGBRenderer_h -#define vpPanda3DRGBRenderer_h +#ifndef VP_PANDA3D_RGB_RENDERER_H +#define VP_PANDA3D_RGB_RENDERER_H #include @@ -40,6 +39,7 @@ #include #include +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d_3d * \brief Implementation of a traditional RGB renderer in Panda3D @@ -60,7 +60,7 @@ * - run gltf2bam path/to/yourObject.gltf path/to/yourObject.bam * - then, in the code, use `renderer.addNodeToScene("/path/to/yourObject.bam");` * - */ +*/ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vpPanda3DLightableScene { public: @@ -115,5 +115,6 @@ class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vp }; +END_VISP_NAMESPACE #endif //VISP_HAVE_PANDA3D #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h index 20b3705568..63c33f7311 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,14 +28,15 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPanda3DRenderParameters_h -#define vpPanda3DRenderParameters_h +#ifndef VP_PANDA3D_RENDER_PARAMETERS_H +#define VP_PANDA3D_RENDER_PARAMETERS_H #include #if defined(VISP_HAVE_PANDA3D) #include +BEGIN_VISP_NAMESPACE class Camera; /** @@ -46,7 +46,7 @@ class Camera; * - Camera intrinsics * - Image resolution * - Clipping parameters - */ +*/ class VISP_EXPORT vpPanda3DRenderParameters { public: @@ -124,5 +124,6 @@ class VISP_EXPORT vpPanda3DRenderParameters double m_clipNear, m_clipFar; }; +END_VISP_NAMESPACE #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h index 2684f37987..dcade55b77 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRendererSet.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,8 +28,9 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#ifndef vpPanda3DRendererSet_h -#define vpPanda3DRendererSet_h +#ifndef VP_PANDA3D_RENDERER_SET_H +#define VP_PANDA3D_RENDERER_SET_H + #include #if defined(VISP_HAVE_PANDA3D) @@ -40,6 +40,7 @@ #include #include +BEGIN_VISP_NAMESPACE /** * \ingroup group_ar_renderer_panda3d * @@ -55,7 +56,7 @@ * 3. Add the subrenderers to the set with addSubRenderer * 4. Call renderFrame() on the rendererSet. Each subrenderer now has its output computed and ready to be retrieved * 5. Retrieve relevant outputs in ViSP format with something similar to `rendererSet.getRenderer("MyRendererName").getRender(I)` where RendererType is the relevant subclass of vpPanda3DBaseRenderer and "MyRendererName" its name (see vpPanda3DBaseRenderer::getName) - */ +*/ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vpPanda3DLightable { public: @@ -125,12 +126,11 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp * * \throws vpException, as this method is not supported * @param object - * @param wTo */ vpHomogeneousMatrix getNodePose(NodePath &object) vp_override; /** - * \warn this method is not supported and will throw + * \warning This method is not supported and will throw */ void addNodeToScene(const NodePath &object) vp_override; @@ -193,6 +193,6 @@ class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vp private: std::vector> m_subRenderers; }; - +END_VISP_NAMESPACE #endif #endif diff --git a/modules/ar/include/visp3/ar/vpSimulator.h b/modules/ar/include/visp3/ar/vpSimulator.h index cf6604a11c..260bad9e9d 100644 --- a/modules/ar/include/visp3/ar/vpSimulator.h +++ b/modules/ar/include/visp3/ar/vpSimulator.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,14 +29,10 @@ * * Description: * Simulator based on Coin3d. - * - * Authors: - * Anthony Saunier - * -*****************************************************************************/ + */ -#ifndef vpSimulator_HH -#define vpSimulator_HH +#ifndef VP_SIMULATOR_H +#define VP_SIMULATOR_H /*! \file vpSimulator.h \brief Implementation of a simulator based on Coin3d (www.coin3d.org). diff --git a/modules/ar/include/visp3/ar/vpSimulatorException.h b/modules/ar/include/visp3/ar/vpSimulatorException.h index f494e16552..e2a545222b 100644 --- a/modules/ar/include/visp3/ar/vpSimulatorException.h +++ b/modules/ar/include/visp3/ar/vpSimulatorException.h @@ -1,6 +1,6 @@ /* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,8 +36,8 @@ * \brief Error that can be emitted by the vpSimulator class and its derivatives */ -#ifndef _vpSimulatorException_h_ -#define _vpSimulatorException_h_ +#ifndef VP_SIMULATOR_EXCEPTION_H +#define VP_SIMULATOR_EXCEPTION_H #include #include diff --git a/modules/ar/include/visp3/ar/vpViewer.h b/modules/ar/include/visp3/ar/vpViewer.h index 4e76034a3f..84d168ef7c 100644 --- a/modules/ar/include/visp3/ar/vpViewer.h +++ b/modules/ar/include/visp3/ar/vpViewer.h @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,8 +29,7 @@ * * Description: * Simulator based on Coin3d. - * -*****************************************************************************/ + */ /*! \file vpViewer.h @@ -45,8 +43,8 @@ */ -#ifndef vpViewer_HH -#define vpViewer_HH +#ifndef VP_VIEWER_H +#define VP_VIEWER_H #include diff --git a/modules/ar/src/coin-simulator/vpAR.cpp b/modules/ar/src/coin-simulator/vpAR.cpp index 9c7c85996d..a2dbafdb69 100644 --- a/modules/ar/src/coin-simulator/vpAR.cpp +++ b/modules/ar/src/coin-simulator/vpAR.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,8 +30,7 @@ * Description: * Use to display an image behind the internal view of the simulator * used for augmented reality application - * -*****************************************************************************/ + */ /*! \file vpAR.cpp @@ -66,17 +64,16 @@ BEGIN_VISP_NAMESPACE /*! - Basic Destructor that calls the kill() method of the vpSimulator - class. + Basic Destructor that calls the kill() method of the vpSimulator class. */ vpAR::~vpAR() { kill(); } /*! - Initialisation of the internal view of the simulator. + Initialisation of the internal view of the simulator. - \param width : Width of the internal view. - \param height : Height of the internal view. - \param type : Type of background image ie gray scaled or color. + \param width : Width of the internal view. + \param height : Height of the internal view. + \param type : Type of background image ie gray scaled or color. */ void vpAR::initInternalViewer(unsigned int width, unsigned int height, vpImageType type) { @@ -99,9 +96,9 @@ void vpAR::initInternalViewer(unsigned int width, unsigned int height, vpImageTy } /*! - Set the background image and turn it to deal with the frame of OpenGL. + Set the background image and turn it to deal with the frame of OpenGL. - \param I : Gray scaled image for the background. + \param I : Gray scaled image for the background. */ // Grey pictures SetBackGroundImage void vpAR::setImage(vpImage &I) @@ -122,14 +119,13 @@ void vpAR::setImage(vpImage &I) } /*! - Set the background image and turn it to deal with the frame of OpenGL. + Set the background image and turn it to deal with the frame of OpenGL. - \param I : Color image for the background. + \param I : Color image for the background. */ // Color pictures SetBackGroundImage void vpAR::setImage(vpImage &I) { - if ((internal_width != I.getWidth()) || (internal_height != I.getHeight())) { vpERROR_TRACE("The image size is different from the view size "); throw(vpException(vpException::dimensionError), "The image size is different from the view size"); diff --git a/modules/ar/src/coin-simulator/vpSimulator.cpp b/modules/ar/src/coin-simulator/vpSimulator.cpp index a3d2da3227..157b736d4b 100644 --- a/modules/ar/src/coin-simulator/vpSimulator.cpp +++ b/modules/ar/src/coin-simulator/vpSimulator.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +29,7 @@ * * Description: * Simulator based on Coin3d. - * - * Authors: - * Anthony Saunier - * -*****************************************************************************/ + */ /*! \file vpSimulator.cpp \brief Implementation of a simulator based on Coin3d (www.coin3d.org). diff --git a/modules/ar/src/coin-simulator/vpViewer.cpp b/modules/ar/src/coin-simulator/vpViewer.cpp index c987ceb469..cfb7703866 100644 --- a/modules/ar/src/coin-simulator/vpViewer.cpp +++ b/modules/ar/src/coin-simulator/vpViewer.cpp @@ -1,5 +1,4 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. * Copyright (C) 2005 - 2023 by Inria. All rights reserved. * @@ -30,8 +29,7 @@ * * Description: * Simulator based on Coin3d. - * -*****************************************************************************/ + */ /*! \file vpViewer.cpp Viewer used by the simulator. Under Windows, the viewer is diff --git a/modules/ar/src/ogre-simulator/vpAROgre.cpp b/modules/ar/src/ogre-simulator/vpAROgre.cpp index e371043efa..199ed37052 100644 --- a/modules/ar/src/ogre-simulator/vpAROgre.cpp +++ b/modules/ar/src/ogre-simulator/vpAROgre.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +29,7 @@ * * Description: * Augmented Reality viewer using Ogre3D. - * - * Authors: - * Bertrand Delabarre - * -*****************************************************************************/ + */ /*! \file vpAROgre.cpp diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp index 7aa31e736c..5fb8d5b285 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DBaseRenderer.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,6 +37,7 @@ #include "load_prc_file.h" #include +BEGIN_VISP_NAMESPACE const vpHomogeneousMatrix vpPanda3DBaseRenderer::VISP_T_PANDA({ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1., 0.0, @@ -74,7 +74,6 @@ void vpPanda3DBaseRenderer::initFramework() //m_window->get_display_region_3d()->set_camera(m_cameraPath); } - void vpPanda3DBaseRenderer::initFromParent(std::shared_ptr framework, PT(WindowFramework) window) { m_framework = framework; @@ -208,11 +207,8 @@ void vpPanda3DBaseRenderer::enableDebugLog() { load_prc_file_data("", "gl-debug 1"); load_prc_file_data("", "notify-level-display spam"); - } - - vpColVector vpPanda3DBaseRenderer::vispPointToPanda(const vpColVector &point) { vpColVector pandaPos = PANDA_T_VISP * point; @@ -225,12 +221,15 @@ vpColVector vpPanda3DBaseRenderer::vispVectorToPanda(const vpColVector &point) return pandaPos; } - - void vpPanda3DBaseRenderer::printStructure() { m_renderRoot.ls(); } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DBaseRenderer.cpp.o) has no symbols +void dummy_vpPanda3DBaseRenderer() { }; #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index 998c848ec2..5ef57c4b48 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -1,8 +1,39 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + #include #include #if defined(VISP_HAVE_PANDA3D) +BEGIN_VISP_NAMESPACE const char *vpPanda3DLuminanceFilter::FRAGMENT_SHADER = R"shader( #version 330 @@ -18,7 +49,6 @@ void main() { } )shader"; - vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput) : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, std::string(vpPanda3DLuminanceFilter::FRAGMENT_SHADER)) { } @@ -35,7 +65,6 @@ void vpPanda3DLuminanceFilter::getRender(vpImage &I) const vpPanda3DPostProcessFilter::getRenderBasic(I); } - const char *vpPanda3DGaussianBlur::FRAGMENT_SHADER = R"shader( #version 330 @@ -181,4 +210,10 @@ void vpPanda3DCanny::getRender(vpImage &I) const vpPanda3DPostProcessFilter::getRenderBasic(I); } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DCanny.cpp.o) has no symbols +void dummy_vpPanda3DCanny() { }; + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp index f431244c09..7700089823 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DGeometryRenderer.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +34,7 @@ #include +BEGIN_VISP_NAMESPACE const char *vpPanda3DGeometryRenderer::SHADER_VERT_NORMAL_AND_DEPTH_CAMERA = R"shader( #version 330 @@ -253,4 +253,10 @@ void vpPanda3DGeometryRenderer::getRender(vpImage &depth) const } } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DGeometryRenderer.cpp.o) has no symbols +void dummy_vpPanda3DGeometryRenderer() { }; + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp index 35e2251ba5..2daa1c9af8 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -1,8 +1,40 @@ -#include +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ +#include #if defined(VISP_HAVE_PANDA3D) +#include + +BEGIN_VISP_NAMESPACE const char *vpPanda3DPostProcessFilter::FILTER_VERTEX_SHADER = R"shader( #version 330 in vec4 p3d_Vertex; @@ -17,7 +49,6 @@ void main() } )shader"; - void vpPanda3DPostProcessFilter::setupScene() { CardMaker cm("cm"); @@ -159,10 +190,14 @@ void vpPanda3DPostProcessFilter::getRenderBasic(vpImage &I) const colorRow[j].B = data[j * numComponents]; colorRow[j].G = data[j * numComponents + 1]; colorRow[j].R = data[j * numComponents + 2]; - } } } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DPostProcessFilter.cpp.o) has no symbols +void dummy_vpPanda3DPostProcessFilter() { }; #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index 93f6bc586e..ab6800fdaf 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +36,7 @@ #include "cardMaker.h" #include "texturePool.h" +BEGIN_VISP_NAMESPACE const char *vpPanda3DRGBRenderer::COOK_TORRANCE_VERT = R"shader( #version 330 @@ -146,7 +146,6 @@ vec3 F(vec3 F0, float vh) return F0 + (vec3(1.f, 1.f, 1.f) - F0) * pow(1.f - vh, 5); } - void main() { vec3 n = normalize(oNormal); // normalized normal vector @@ -241,7 +240,6 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) setNodePose(objectInScene, vpHomogeneousMatrix()); } - void vpPanda3DRGBRenderer::setBackgroundImage(const vpImage &background) { @@ -287,7 +285,6 @@ void vpPanda3DRGBRenderer::setBackgroundImage(const vpImage &background) destRow[j * 4 + 3] = srcRow[j].A; } } - } void vpPanda3DRGBRenderer::getRender(vpImage &I) const @@ -311,8 +308,6 @@ void vpPanda3DRGBRenderer::getRender(vpImage &I) const } data += rowIncrement; } - - } void vpPanda3DRGBRenderer::setupScene() @@ -334,7 +329,6 @@ void vpPanda3DRGBRenderer::setupRenderTarget() fbp.set_rgba_bits(8, 8, 8, 8); fbp.set_srgb_color(true); - WindowProperties win_prop; win_prop.set_size(m_renderParameters.getImageWidth(), m_renderParameters.getImageHeight()); @@ -366,4 +360,10 @@ void vpPanda3DRGBRenderer::setupRenderTarget() region->set_clear_color(LColor(0.f)); } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DRGBRenderer.cpp.o) has no symbols +void dummy_vpPanda3DRGBRenderer() { }; + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp index 9a1e16d9dc..43a4910359 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRenderParameters.cpp @@ -1,8 +1,7 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +35,7 @@ #include #include +BEGIN_VISP_NAMESPACE void vpPanda3DRenderParameters::setupPandaCamera(Camera *camera) { // Adapted from Megapose code (https://github.com/megapose6d/megapose6d/blob/master/src/megapose/panda3d_renderer/types.py#L59), @@ -65,4 +65,10 @@ void vpPanda3DRenderParameters::setupPandaCamera(Camera *camera) camera->set_lens(lens); } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DRenderParameters.cpp.o) has no symbols +void dummy_vpPanda3DRenderParameters() { }; + #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 35fb229243..3f1e0849a6 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +34,7 @@ #include "load_prc_file.h" +BEGIN_VISP_NAMESPACE vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") { m_renderParameters = renderParameters; @@ -167,7 +167,7 @@ void vpPanda3DRendererSet::addSubRenderer(std::shared_ptr ++it; } m_subRenderers.insert(it, renderer); - for (const auto r: m_subRenderers) { + for (const auto &r: m_subRenderers) { std::cout << r->getName() << " "; } std::cout << std::endl; @@ -179,4 +179,10 @@ void vpPanda3DRendererSet::addSubRenderer(std::shared_ptr } } +END_VISP_NAMESPACE + +#elif !defined(VISP_BUILD_SHARED_LIBS) +// Work around to avoid warning: libvisp_ar.a(vpPanda3DRendererSet.cpp.o) has no symbols +void dummy_vpPanda3DRendererSet() { }; + #endif diff --git a/modules/ar/src/vpSimulatorException.cpp b/modules/ar/src/vpSimulatorException.cpp index a8049c364a..3c70f6d9fb 100644 --- a/modules/ar/src/vpSimulatorException.cpp +++ b/modules/ar/src/vpSimulatorException.cpp @@ -1,7 +1,6 @@ -/**************************************************************************** - * +/* * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,13 +29,11 @@ * * Description: * Exceptions that can be emitted by the simulator classes. - * -*****************************************************************************/ + */ -/* \file vpSimulatorException.h +/*! \file vpSimulatorException.h \brief error that can be emitted by the vpSimulator class and its derivatives */ -/* Classes standards. */ #include diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index ffa71f4cf2..84b357cf9c 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -1,36 +1,4 @@ -/**************************************************************************** - * - * ViSP, open source Visual Servoing Platform software. - * Copyright (C) 2005 - 2023 by Inria. All rights reserved. - * - * This software is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * See the file LICENSE.txt at the root directory of this source - * distribution for additional information about the GNU GPL. - * - * For using ViSP with software that can not be combined with the GNU - * GPL, please contact Inria about acquiring a ViSP Professional - * Edition License. - * - * See https://visp.inria.fr for more information. - * - * This software was developed at: - * Inria Rennes - Bretagne Atlantique - * Campus Universitaire de Beaulieu - * 35042 Rennes Cedex - * France - * - * If you have questions regarding the use of this file, please contact - * Inria at visp@inria.fr - * - * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE - * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - */ - //! \example tutorial-panda3d-renderer.cpp - #include #include @@ -53,10 +21,16 @@ #include #include +#ifdef ENABLE_VISP_NAMESPACE +using namespace VISP_NAMESPACE_NAME; +#endif + void displayNormals(const vpImage &normalsImage, vpImage &normalDisplayImage) { +#if defined(_OPENMP) #pragma omp parallel for +#endif for (int i = 0; i < normalsImage.getSize(); ++i) { normalDisplayImage.bitmap[i].R = static_cast((normalsImage.bitmap[i].R + 1.0) * 127.5f); normalDisplayImage.bitmap[i].G = static_cast((normalsImage.bitmap[i].G + 1.0) * 127.5f); @@ -66,10 +40,13 @@ void displayNormals(const vpImage &normalsImage, vpDisplay::display(normalDisplayImage); vpDisplay::flush(normalDisplayImage); } + void displayDepth(const vpImage &depthImage, vpImage &depthDisplayImage, float nearV, float farV) { +#if defined(_OPENMP) #pragma omp parallel for +#endif for (int i = 0; i < depthImage.getSize(); ++i) { float val = std::max(0.f, (depthImage.bitmap[i] - nearV) / (farV - nearV)); depthDisplayImage.bitmap[i] = static_cast(val * 255.f); @@ -78,10 +55,11 @@ void displayDepth(const vpImage &depthImage, vpDisplay::flush(depthDisplayImage); } - void displayLightDifference(const vpImage &colorImage, const vpImage &colorDiffuseOnly, vpImage &lightDifference) { +#if defined(_OPENMP) #pragma omp parallel for +#endif for (int i = 0; i < colorImage.getSize(); ++i) { float I1 = 0.299 * colorImage.bitmap[i].R + 0.587 * colorImage.bitmap[i].G + 0.114 * colorImage.bitmap[i].B; float I2 = 0.299 * colorDiffuseOnly.bitmap[i].R + 0.587 * colorDiffuseOnly.bitmap[i].G + 0.114 * colorDiffuseOnly.bitmap[i].B; @@ -90,10 +68,13 @@ void displayLightDifference(const vpImage &colorImage, const vpImage &cannyRawData, vpImage &canny) { +#if defined(_OPENMP) #pragma omp parallel for +#endif for (int i = 0; i < cannyRawData.getSize(); ++i) { vpRGBf &px = cannyRawData.bitmap[i]; canny.bitmap[i] = 255 * (px.R * px.R + px.G * px.G > 0); @@ -174,21 +155,14 @@ int main(int argc, const char **argv) //! [Renderer set] //! [Subrenderers init] - std::shared_ptr geometryRenderer = - std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::OBJECT_NORMALS); - std::shared_ptr cameraRenderer = - std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); - std::shared_ptr rgbRenderer = - std::make_shared(); - std::shared_ptr rgbDiffuseRenderer = - std::make_shared(false); - std::shared_ptr grayscaleFilter = - std::make_shared("toGrayscale", rgbRenderer, false); - std::shared_ptr blurFilter = - std::make_shared("blur", grayscaleFilter, false); - std::shared_ptr cannyFilter = - std::make_shared("canny", blurFilter, true, 10.f); - //! [Subrenderers] + std::shared_ptr geometryRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::OBJECT_NORMALS); + std::shared_ptr cameraRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); + std::shared_ptr rgbRenderer = std::make_shared(); + std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); + std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); + std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); + std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 10.f); + //! [Subrenderers init] //! [Adding subrenderers] renderer.addSubRenderer(geometryRenderer); @@ -219,25 +193,19 @@ int main(int argc, const char **argv) vpPanda3DDirectionalLight dlight("Directional", vpRGBf(2.0), vpColVector({ 1.0, 1.0, 0.0 })); renderer.addLight(dlight); - <<<<<<< HEAD - rgbRenderer->printStructure(); if (!backgroundPath.empty()) { vpImage background; vpImageIo::read(background, backgroundPath); rgbRenderer->setBackgroundImage(background); } + rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); - ====== = - renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); - //! [Scene configuration] - - rgbRenderer->printStructure(); + //! [Scene configuration] - >>>>>>> c386b93ae(tutorial structure) - unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); + unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); std::cout << "Creating display and data images" << std::endl; vpImage normalsImage; vpImage cameraNormalsImage; From f287e3fa22340c18ba59133d6341aeb03a5990d5 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 11 Jun 2024 12:10:03 +0200 Subject: [PATCH 64/65] Fix build when ENABLE_VISP_NAMESPACE is turned ON --- modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h index 63c33f7311..3a4cf51584 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DRenderParameters.h @@ -36,9 +36,9 @@ #if defined(VISP_HAVE_PANDA3D) #include -BEGIN_VISP_NAMESPACE class Camera; +BEGIN_VISP_NAMESPACE /** * @brief Rendering parameters for a panda3D simulation * From 06791ed8ac2ea8601c4b392767238af142f1fd25 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Tue, 11 Jun 2024 12:16:33 +0200 Subject: [PATCH 65/65] Update with Panda3D changes --- ChangeLog.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.txt b/ChangeLog.txt index 8b069dd5c4..df585eecb7 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -37,6 +37,8 @@ ViSP 3.x.x (Version in development) . New vpImageTools::inRange() functions to ease binary mask computation by thresholding HSV channels . New tutorials in tutorial/segmentation/color folder to show how to use HSV color segmentation to extract the corresponding point cloud + . New scene renderer based on Panda3D. See inheritance diagram for vpPanda3DBaseRenderer class and corresponding + tutorial. - Applications . Migrate eye-to-hand tutorials in apps - Tutorials @@ -57,6 +59,8 @@ ViSP 3.x.x (Version in development) https://visp-doc.inria.fr/doxygen/visp-daily/tutorial-hsv-segmentation-live.html . New tutorial: Point cloud segmentation using HSV color scale https://visp-doc.inria.fr/doxygen/visp-daily/tutorial-hsv-segmentation-pcl.html + . New tutorial: Rendering a 3D scene with Panda3D + https://visp-doc.inria.fr/doxygen/visp-daily/tutorial-panda3d.html - Bug fixed . [#1251] Bug in vpDisplay::displayFrame() . [#1270] Build issue around std::clamp and optional header which are not found with cxx17