diff --git a/apps/SceneViewer3D/_DSceneViewerMain.cpp b/apps/SceneViewer3D/_DSceneViewerMain.cpp index 8556673ce4..4e65081369 100644 --- a/apps/SceneViewer3D/_DSceneViewerMain.cpp +++ b/apps/SceneViewer3D/_DSceneViewerMain.cpp @@ -891,32 +891,43 @@ 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); + std::cerr << mrpt::exception_to_str(e) << std::endl; + wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this); + } + } - 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); - } + // Enable shadows? + if (0) + { + if (auto vw = m_canvas->getOpenGLSceneRef()->getViewport(); vw) + { + vw->enableShadowCasting(); } } } diff --git a/doc/source/doxygen-docs/changelog.md b/doc/source/doxygen-docs/changelog.md index 9b2dc93c39..35c6c45efb 100644 --- a/doc/source/doxygen-docs/changelog.md +++ b/doc/source/doxygen-docs/changelog.md @@ -1,7 +1,9 @@ \page changelog Change Log # Version 2.14.1: UNRELEASED -(None yet) +- 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. # 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/src/CAssimpModel.cpp b/libs/opengl/src/CAssimpModel.cpp index bb5fea53e4..96d6d0f6fc 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,14 @@ 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; + + CRenderizable::notifyChange(); + if (m_assimp_scene->scene) after_load_model(); +} + bool CAssimpModel::traceRay( [[maybe_unused]] const mrpt::poses::CPose3D& o, [[maybe_unused]] double& dist) const {