diff --git a/apps/SceneViewer3D/_DSceneViewerMain.cpp b/apps/SceneViewer3D/_DSceneViewerMain.cpp index 8556673ce4..0e571cad18 100644 --- a/apps/SceneViewer3D/_DSceneViewerMain.cpp +++ b/apps/SceneViewer3D/_DSceneViewerMain.cpp @@ -320,6 +320,7 @@ const long _DSceneViewerFrame::ID_BUTTON5 = wxNewId(); const long _DSceneViewerFrame::ID_STATICLINE2 = wxNewId(); const long _DSceneViewerFrame::ID_BUTTON6 = wxNewId(); const long _DSceneViewerFrame::ID_BUTTON7 = wxNewId(); +const long _DSceneViewerFrame::ID_BUTTON_SHADOWS = wxNewId(); const long _DSceneViewerFrame::ID_BUTTON8 = wxNewId(); const long _DSceneViewerFrame::ID_BUTTON9 = wxNewId(); const long _DSceneViewerFrame::ID_STATICLINE3 = wxNewId(); @@ -480,6 +481,17 @@ _DSceneViewerFrame::_DSceneViewerFrame(wxWindow* parent, wxWindowID id) : maxv(0 wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR)); btnOrtho->SetMargins(wxSize(5, 5)); FlexGridSizer2->Add(btnOrtho, 1, wxEXPAND, 1); + + btnShadows = new wxCustomButton( + this, ID_BUTTON_SHADOWS, _(" Shadows "), + wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR), + wxDefaultPosition, wxDefaultSize, wxCUSTBUT_TOGGLE | wxCUSTBUT_BOTTOM, wxDefaultValidator, + _T("ID_BUTTON_SHADOWS")); + btnShadows->SetBitmapDisabled( + wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR)); + btnShadows->SetMargins(wxSize(5, 5)); + FlexGridSizer2->Add(btnShadows, 1, wxEXPAND, 1); + btnAutoplay = new wxCustomButton( this, ID_BUTTON8, _(" Autoplay "), wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REMOVABLE")), wxART_TOOLBAR), @@ -657,6 +669,7 @@ _DSceneViewerFrame::_DSceneViewerFrame(wxWindow* parent, wxWindowID id) : maxv(0 Bind(wxEVT_BUTTON, &svf::OnReload, this, ID_BUTTON5); Bind(wxEVT_BUTTON, &svf::OnMenuOptions, this, ID_BUTTON6); Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnOrthoClicked, this, ID_BUTTON7); + Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnShadowsClicked, this, ID_BUTTON_SHADOWS); Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnAutoplayClicked, this, ID_BUTTON8); Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnBtnRecordClicked, this, ID_BUTTON9); Bind(wxEVT_BUTTON, &svf::OnAbout, this, ID_BUTTON10); @@ -891,34 +904,39 @@ void _DSceneViewerFrame::OntimLoadFileCmdLineTrigger(wxTimerEvent&) { timLoadFileCmdLine.Stop(); // One shot only. // Open file if passed by the command line: - if (!global_fileToOpen.empty()) + if (global_fileToOpen.empty()) return; + + if (mrpt::system::strCmpI( + "3Dscene", mrpt::system::extractFileExtension(global_fileToOpen, true /*ignore .gz*/))) { - if (mrpt::system::strCmpI( - "3Dscene", mrpt::system::extractFileExtension(global_fileToOpen, true /*ignore .gz*/))) + loadFromFile(global_fileToOpen); + } + else + { + std::cout << "Filename extension does not match `3Dscene`, " + "importing as an ASSIMP model...\n"; + try { - loadFromFile(global_fileToOpen); + auto obj3D = mrpt::opengl::CAssimpModel::Create(); + obj3D->loadScene(global_fileToOpen); + // obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg, + // 0._deg, 90.0_deg)); + m_canvas->getOpenGLSceneRef()->insert(obj3D); + + // TODO: make optional? + obj3D->split_triangles_rendering_bbox(0.25); + + m_canvas->Refresh(); } - else + catch (const std::exception& e) { - std::cout << "Filename extension does not match `3Dscene`, " - "importing as an ASSIMP model...\n"; - try - { - auto obj3D = mrpt::opengl::CAssimpModel::Create(); - obj3D->loadScene(global_fileToOpen); - // obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg, - // 0._deg, 90.0_deg)); - m_canvas->getOpenGLSceneRef()->insert(obj3D); - - m_canvas->Refresh(); - } - catch (const std::exception& e) - { - std::cerr << mrpt::exception_to_str(e) << std::endl; - wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this); - } + std::cerr << mrpt::exception_to_str(e) << std::endl; + wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this); } } + + // Enable shadows? + applyShadowsOptions(); } void _DSceneViewerFrame::OnbtnAutoplayClicked(wxCommandEvent& event) @@ -1037,6 +1055,24 @@ void _DSceneViewerFrame::OnbtnOrthoClicked(wxCommandEvent& event) m_canvas->Refresh(false); } +void _DSceneViewerFrame::OnbtnShadowsClicked(wxCommandEvent& event) +{ + applyShadowsOptions(); + m_canvas->Refresh(false); +} + +void _DSceneViewerFrame::applyShadowsOptions() +{ + bool shadowsEnabled = btnShadows->GetValue(); + auto scene = m_canvas->getOpenGLSceneRef(); + if (!scene) return; + + auto vw = scene->getViewport(); + if (!vw) return; + + vw->enableShadowCasting(shadowsEnabled); +} + void _DSceneViewerFrame::OnReload(wxCommandEvent& event) { if (fileExists(loadedFileName)) loadFromFile(loadedFileName); diff --git a/apps/SceneViewer3D/_DSceneViewerMain.h b/apps/SceneViewer3D/_DSceneViewerMain.h index 3fc110ab8a..23dfae6d9d 100644 --- a/apps/SceneViewer3D/_DSceneViewerMain.h +++ b/apps/SceneViewer3D/_DSceneViewerMain.h @@ -76,6 +76,7 @@ class _DSceneViewerFrame : public wxFrame void OnClose(wxCloseEvent& event); void OnBtnRecordClicked(wxCommandEvent& event); void OnbtnOrthoClicked(wxCommandEvent& event); + void OnbtnShadowsClicked(wxCommandEvent& event); void OnReload(wxCommandEvent& event); void OnInsert3DS(wxCommandEvent& event); void OnMenuSave(wxCommandEvent& event); @@ -104,6 +105,8 @@ class _DSceneViewerFrame : public wxFrame void OntimAutoplay(wxTimerEvent& event); + void applyShadowsOptions(); + //(*Identifiers(_DSceneViewerFrame) static const long ID_BUTTON1; static const long ID_BUTTON2; @@ -114,6 +117,7 @@ class _DSceneViewerFrame : public wxFrame static const long ID_STATICLINE2; static const long ID_BUTTON6; static const long ID_BUTTON7; + static const long ID_BUTTON_SHADOWS; static const long ID_BUTTON8; static const long ID_BUTTON9; static const long ID_STATICLINE3; @@ -187,6 +191,7 @@ class _DSceneViewerFrame : public wxFrame wxMenuItem* mnuSelectionScale; wxMenuItem* mnuImportLAS; wxCustomButton* btnOrtho; + wxCustomButton* btnShadows; wxStatusBar* StatusBar1; wxMenuItem* MenuItem6; wxStaticLine* StaticLine3; diff --git a/doc/source/doxygen-docs/changelog.md b/doc/source/doxygen-docs/changelog.md index 9b2dc93c39..0d8e9b977d 100644 --- a/doc/source/doxygen-docs/changelog.md +++ b/doc/source/doxygen-docs/changelog.md @@ -1,7 +1,12 @@ \page changelog Change Log # Version 2.14.1: UNRELEASED -(None yet) +- Changes in apps: + - SceneViewer3D: New button to enable shadow casting. +- Changes in libraries: + - \ref mrpt_opengl_grp: + - New method mrpt::opengl::CAssimpModel::split_triangles_rendering_bbox() to enable a new feature in Assimp 3D models: splitting into smaller triangle sets for correct z-ordering of semitransparent objects; e.g. required for trees with masked leaves. + - SkyBox shader changed its internal ID so it gets rendered before potentially transparent elements, fixing render artifacts. # Version 2.14.0: Released Sep 15th, 2024 - Changes in libraries: diff --git a/libs/opengl/include/mrpt/opengl/CAssimpModel.h b/libs/opengl/include/mrpt/opengl/CAssimpModel.h index a5e1c1f0bd..b5b9d68e79 100644 --- a/libs/opengl/include/mrpt/opengl/CAssimpModel.h +++ b/libs/opengl/include/mrpt/opengl/CAssimpModel.h @@ -120,6 +120,16 @@ class CAssimpModel : mrpt::math::TBoundingBoxf internalBoundingBoxLocal() const override; + /** Enable (or disable if set to .0f) a feature in which textured triangles + * are split into different renderizable smaller objects. + * This is required only for semitransparent objects with overlaping regions. + */ + void split_triangles_rendering_bbox(const float bbox_size); + [[nodiscard]] float split_triangles_rendering_bbox() const + { + return m_split_triangles_rendering_bbox; + } + struct TInfoPerTexture { // indices in \a m_texturedObjects. string::npos for non-initialized @@ -152,6 +162,7 @@ class CAssimpModel : mutable std::vector m_texturedObjects; bool m_verboseLoad = false; bool m_ignoreMaterialColor = false; + float m_split_triangles_rendering_bbox = .0f; void recursive_render( const aiScene* sc, diff --git a/libs/opengl/include/mrpt/opengl/DefaultShaders.h b/libs/opengl/include/mrpt/opengl/DefaultShaders.h index e42d9a05ab..1e77a53b21 100644 --- a/libs/opengl/include/mrpt/opengl/DefaultShaders.h +++ b/libs/opengl/include/mrpt/opengl/DefaultShaders.h @@ -26,19 +26,19 @@ struct DefaultShaderID static constexpr shader_id_t POINTS = 0; static constexpr shader_id_t WIREFRAME = 1; static constexpr shader_id_t TEXT = 2; - static constexpr shader_id_t TRIANGLES_LIGHT = 3; - static constexpr shader_id_t TEXTURED_TRIANGLES_LIGHT = 4; - static constexpr shader_id_t TRIANGLES_NO_LIGHT = 5; - static constexpr shader_id_t TEXTURED_TRIANGLES_NO_LIGHT = 6; + static constexpr shader_id_t TRIANGLES_LIGHT = 10; + static constexpr shader_id_t TEXTURED_TRIANGLES_LIGHT = 11; + static constexpr shader_id_t TRIANGLES_NO_LIGHT = 12; + static constexpr shader_id_t TEXTURED_TRIANGLES_NO_LIGHT = 13; // Shadow generation 1st/2nd pass shaders: - static constexpr shader_id_t TRIANGLES_SHADOW_1ST = 10; - static constexpr shader_id_t TRIANGLES_SHADOW_2ND = 11; - static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_1ST = 12; - static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_2ND = 13; + static constexpr shader_id_t TRIANGLES_SHADOW_1ST = 20; + static constexpr shader_id_t TRIANGLES_SHADOW_2ND = 21; + static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_1ST = 22; + static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_2ND = 23; // Special effects: - static constexpr shader_id_t SKYBOX = 20; + static constexpr shader_id_t SKYBOX = 5; // render *before* potentially transparent triangles static constexpr shader_id_t DEBUG_TEXTURE_TO_SCREEN = 30; static constexpr shader_id_t NONE = 31; //!< Skip rendering }; diff --git a/libs/opengl/src/CAssimpModel.cpp b/libs/opengl/src/CAssimpModel.cpp index bb5fea53e4..64505cef26 100644 --- a/libs/opengl/src/CAssimpModel.cpp +++ b/libs/opengl/src/CAssimpModel.cpp @@ -33,6 +33,7 @@ #endif #include +#include #include #include #include @@ -273,8 +274,65 @@ void CAssimpModel::onUpdateBuffers_all() process_textures(m_assimp_scene->scene); + // Model -> 3D primitives: const auto transf = mrpt::poses::CPose3D(); recursive_render(m_assimp_scene->scene, m_assimp_scene->scene->mRootNode, transf, re); + + // Handle split: + if (m_split_triangles_rendering_bbox > .0f) + { + const float voxel = m_split_triangles_rendering_bbox; + + ASSERT_EQUAL_(m_texturedObjects.size(), m_textureIdMap.size()); + const std::vector origTexturedObjects = m_texturedObjects; + m_texturedObjects.clear(); + + std::unordered_map> + newTextObjs; + + for (size_t textId = 0; textId < m_textureIdMap.size(); textId++) + { + const auto& glTris = origTexturedObjects.at(textId); + const std::vector& iTris = glTris->shaderTexturedTrianglesBuffer(); + for (const auto& t : iTris) + { + const auto midPt = 0.333f * (t.vertices[0].xyzrgba.pt + t.vertices[1].xyzrgba.pt + + t.vertices[2].xyzrgba.pt); + // hash for "voxels": + const int64_t vxlCoord = mrpt::round(midPt.x / voxel) + // + mrpt::round(midPt.y / voxel) * 10'000 + // + mrpt::round(midPt.z / voxel) * 100'000'000; + + auto& trgObj = newTextObjs[vxlCoord][textId]; + if (!trgObj) + { + // Create + trgObj = CSetOfTexturedTriangles::Create(); + + // Set representative point: the MOST FUNDAMENTAL property + // to ensure proper z ordering for transparent rendering: + trgObj->setLocalRepresentativePoint(midPt); + + // copy texture + const auto& texRGB = glTris->getTextureImage(); + const auto& texAlpha = glTris->getTextureAlphaImage(); + if (!texAlpha.isEmpty()) + trgObj->assignImage(texRGB, texAlpha); + else + trgObj->assignImage(texRGB); + } + // Add triangle: + trgObj->insertTriangle(t); + } + } + + // replace list of objects: + m_texturedObjects.clear(); + for (const auto& m : newTextObjs) + for (const auto& [k, v] : m.second) // + m_texturedObjects.push_back(v); + } // end: split into subobjects: + #endif } @@ -297,14 +355,15 @@ void CAssimpModel::enqueueForRenderRecursive( mrpt::opengl::enqueueForRendering(lst, state, rq, wholeInView, is1stShadowMapPass); } -uint8_t CAssimpModel::serializeGetVersion() const { return 2; } +uint8_t CAssimpModel::serializeGetVersion() const { return 3; } void CAssimpModel::serializeTo(mrpt::serialization::CArchive& out) const { writeToStreamRender(out); #if (MRPT_HAS_OPENGL_GLUT || MRPT_HAS_EGL) && MRPT_HAS_ASSIMP const bool empty = m_assimp_scene->scene == nullptr; out << empty; - out << m_modelPath; // v2 + out << m_modelPath; // v2 + out << m_split_triangles_rendering_bbox; // v3 if (!empty) { #if 0 @@ -348,6 +407,8 @@ void CAssimpModel::serializeFrom(mrpt::serialization::CArchive& in, uint8_t vers { const bool empty = in.ReadAs(); in >> m_modelPath; + if (version >= 3) in >> m_split_triangles_rendering_bbox; + if (!empty) { const auto blobSize = in.ReadAs(); @@ -439,6 +500,7 @@ void CAssimpModel::after_load_model() // Evaluate overall bbox: { aiVector3D scene_min, scene_max; + ASSERT_(m_assimp_scene->scene); get_bounding_box(m_assimp_scene->scene, &scene_min, &scene_max); m_bbox_min.x = scene_min.x; m_bbox_min.y = scene_min.y; @@ -460,6 +522,16 @@ auto CAssimpModel::internalBoundingBoxLocal() const -> mrpt::math::TBoundingBoxf return mrpt::math::TBoundingBoxf::FromUnsortedPoints(m_bbox_min, m_bbox_max); } +void CAssimpModel::split_triangles_rendering_bbox(const float bbox_size) +{ + m_split_triangles_rendering_bbox = bbox_size; + +#if (MRPT_HAS_OPENGL_GLUT || MRPT_HAS_EGL) && MRPT_HAS_ASSIMP + CRenderizable::notifyChange(); + if (m_assimp_scene->scene) after_load_model(); +#endif +} + bool CAssimpModel::traceRay( [[maybe_unused]] const mrpt::poses::CPose3D& o, [[maybe_unused]] double& dist) const { diff --git a/libs/ros2bridge/src/point_cloud2.cpp b/libs/ros2bridge/src/point_cloud2.cpp index a7de829f25..e83f9234e9 100644 --- a/libs/ros2bridge/src/point_cloud2.cpp +++ b/libs/ros2bridge/src/point_cloud2.cpp @@ -126,7 +126,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CSimple { const unsigned char* msg_data = row_data + col * msg.point_step; - float x, y, z; + float x = 0, y = 0, z = 0; get_float_from_field(x_field, msg_data, x); get_float_from_field(y_field, msg_data, y); get_float_from_field(z_field, msg_data, z); @@ -165,7 +165,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CPoints { const unsigned char* msg_data = row_data + col * msg.point_step; - float x, y, z, i; + float x = 0, y = 0, z = 0, i = 0; get_float_from_field(x_field, msg_data, x); get_float_from_field(y_field, msg_data, y); get_float_from_field(z_field, msg_data, z); @@ -212,7 +212,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CPoints { const unsigned char* msg_data = row_data + col * msg.point_step; - float x, y, z; + float x = 0, y = 0, z = 0; get_float_from_field(x_field, msg_data, x); get_float_from_field(y_field, msg_data, y); get_float_from_field(z_field, msg_data, z); @@ -220,7 +220,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CPoints if (i_field) { - float i; + float i = 0; get_float_from_field(i_field, msg_data, i); obj.setPointIntensity(idx, i); }