17 #include <mrpt/gui/CDisplayWindowGUI.h>
21 #include <mrpt/3rdparty/tclap/CmdLine.h>
22 #include <mrpt/config.h>
23 #include <mrpt/config/CConfigFile.h>
24 #include <mrpt/core/round.h>
25 #include <mrpt/math/TObject3D.h>
26 #include <mrpt/math/geometry.h>
27 #include <mrpt/opengl/CArrow.h>
28 #include <mrpt/opengl/CGridPlaneXY.h>
29 #include <mrpt/opengl/COpenGLScene.h>
30 #include <mrpt/opengl/stock_objects.h>
31 #include <mrpt/poses/CPose3DInterpolator.h>
32 #include <mrpt/system/filesystem.h>
33 #include <mrpt/system/os.h>
34 #include <mrpt/system/string_utils.h>
38 #include "../libcfgpath/cfgpath.h"
47 static TCLAP::UnlabeledValueArg<std::string>
argMapFile(
48 "input",
"Load this metric map file (*.mm)",
false,
"myMap.mm",
"myMap.mm",
53 "One or more (comma separated) *.so files to load as plugins",
false,
54 "foobar.so",
"foobar.so",
cmd);
58 "Also draw a trajectory, given by a TUM file trajectory.",
false,
59 "trajectory.tum",
"trajectory.tum",
cmd);
64 auto glVizMap = mrpt::opengl::CSetOfObjects::Create();
65 auto glGrid = mrpt::opengl::CGridPlaneXY::Create();
66 mrpt::opengl::CSetOfObjects::Ptr glENUCorner, glMapCorner;
67 mrpt::opengl::CSetOfObjects::Ptr glTrajectory;
69 mrpt::gui::CDisplayWindowGUI::Ptr win;
71 std::array<nanogui::TextBox*, 2> lbMapStats = {
nullptr,
nullptr};
72 nanogui::CheckBox* cbApplyGeoRef =
nullptr;
73 nanogui::CheckBox* cbViewOrtho =
nullptr;
74 nanogui::CheckBox* cbView2D =
nullptr;
75 nanogui::CheckBox* cbViewVoxelsAsPoints =
nullptr;
76 nanogui::CheckBox* cbViewVoxelsFreeSpace =
nullptr;
77 nanogui::CheckBox* cbColorizeMap =
nullptr;
78 nanogui::CheckBox* cbKeepOriginalCloudColors =
nullptr;
79 nanogui::CheckBox* cbShowGroundGrid =
nullptr;
80 nanogui::Slider* slPointSize =
nullptr;
81 nanogui::Slider* slTrajectoryThickness =
nullptr;
82 nanogui::Slider* slMidDepthField =
nullptr;
83 nanogui::Slider* slThicknessDepthField =
nullptr;
84 nanogui::Slider* slCameraFOV =
nullptr;
85 nanogui::Label* lbCameraFOV =
nullptr;
86 nanogui::Label* lbMousePos =
nullptr;
87 nanogui::Label* lbCameraPointing =
nullptr;
88 nanogui::Label *lbDepthFieldValues =
nullptr, *lbDepthFieldMid =
nullptr,
89 *lbDepthFieldThickness =
nullptr, *lbPointSize =
nullptr;
90 nanogui::Label* lbTrajThick =
nullptr;
91 nanogui::Widget* panelLayers =
nullptr;
92 nanogui::ComboBox* cbTravellingKeys =
nullptr;
93 nanogui::TextBox* edAnimFPS =
nullptr;
94 nanogui::Slider* slAnimProgress =
nullptr;
95 nanogui::Button * btnAnimate =
nullptr, *btnAnimStop =
nullptr;
96 nanogui::ComboBox* cbTravellingInterp =
nullptr;
98 std::vector<std::string> layerNames;
99 std::map<std::string, nanogui::CheckBox*> cbLayersByName;
100 bool doFitView =
false;
106 mrpt::poses::CPose3DInterpolator trajectory;
109 mrpt::poses::CPose3DInterpolator camTravelling;
110 std::optional<double> camTravellingCurrentTime;
112 constexpr
float TRAV_ZOOM2ROLL = 1e-4;
114 static void rebuild_3d_view();
115 static void onSaveLayers();
122 std::cout <<
"Loading map file: " << mapFile << std::endl;
126 std::cerr <<
"Error loading metric map from file!" << std::endl;
129 theMapFileName = mapFile;
134 for (
const auto& [name, map] : theMap.
layers) layerNames.push_back(name);
137 for (
const auto& [name, map] : theMap.
layers)
145 "sanity check did not pass for layer: '%s'", name.c_str()));
149 void updateMouseCoordinates()
151 const auto mousexY = win->mousePos();
153 mrpt::math::TLine3D mouse_ray;
154 win->background_scene->getViewport(
"main")->get3DRayForPixelCoord(
155 mousexY.x(), mousexY.y(), mouse_ray);
158 using mrpt::math::TPoint3D;
160 const mrpt::math::TPlane ground_plane(
161 TPoint3D(0, 0, 0), TPoint3D(1, 0, 0), TPoint3D(0, 1, 0));
163 mrpt::math::TObject3D inters;
164 mrpt::math::intersect(mouse_ray, ground_plane, inters);
166 mrpt::math::TPoint3D inters_pt;
167 if (inters.getPoint(inters_pt))
169 lbMousePos->setCaption(mrpt::format(
170 "Mouse pointing to: X=%6.03f Y=%6.03f", inters_pt.x, inters_pt.y));
174 void updateCameraLookCoordinates()
176 lbCameraPointing->setCaption(mrpt::format(
177 "Looking at: X=%6.03f Y=%6.03f Z=%6.03f",
178 win->camera().getCameraPointingX(), win->camera().getCameraPointingY(),
179 win->camera().getCameraPointingZ()));
182 void observeViewOptions()
184 if (cbView2D->checked())
186 win->camera().setAzimuthDegrees(-90.0
f);
187 win->camera().setElevationDegrees(90.0
f);
191 void updateMiniCornerView()
193 auto gl_view = win->background_scene->getViewport(
"small-view");
194 if (!gl_view)
return;
196 mrpt::opengl::CCamera& view_cam = gl_view->getCamera();
198 view_cam.setAzimuthDegrees(win->camera().getAzimuthDegrees());
199 view_cam.setElevationDegrees(win->camera().getElevationDegrees());
200 view_cam.setZoomDistance(5);
203 void rebuildLayerCheckboxes()
205 ASSERT_(panelLayers);
206 while (panelLayers->childCount())
208 panelLayers->removeChild(0);
211 for (
size_t i = 0; i < layerNames.size(); i++)
213 auto cb = panelLayers->add<nanogui::CheckBox>(layerNames.at(i));
214 cb->setChecked(
true);
215 cb->setCallback([](
bool) { rebuild_3d_view(); });
218 cbLayersByName[layerNames.at(i)] = cb;
222 void rebuildCamTravellingCombo()
224 std::vector<std::string> lst, lstShort;
225 for (
size_t i = 0; i < camTravelling.size(); i++)
227 auto it = camTravelling.begin();
230 lstShort.push_back(std::to_string(i));
231 lst.push_back(mrpt::format(
232 "[%02u] t=%.02fs pose=%s",
static_cast<unsigned int>(i),
233 mrpt::Clock::toDouble(it->first), it->second.asString().c_str()));
235 cbTravellingKeys->setItems(lst, lstShort);
237 if (!lst.empty()) cbTravellingKeys->setSelectedIndex(lst.size() - 1);
238 win->performLayout();
241 void onKeyboardAction(
int key)
245 constexpr
float SLIDE_VELOCITY = 0.01;
246 constexpr
float SENSIBILITY_ROTATE = 1.0;
248 auto cam = win->camera().cameraParams();
257 const float dx = std::cos(DEG2RAD(cam.cameraAzimuthDeg));
258 const float dy = std::sin(DEG2RAD(cam.cameraAzimuthDeg));
260 (key == GLFW_KEY_UP || key == GLFW_KEY_W) ? -1.0
f : 1.0
f;
261 cam.cameraPointingX +=
262 d * dx * cam.cameraZoomDistance * SLIDE_VELOCITY;
263 cam.cameraPointingY +=
264 d * dy * cam.cameraZoomDistance * SLIDE_VELOCITY;
265 win->camera().setCameraParams(cam);
272 const float dx = std::cos(DEG2RAD(cam.cameraAzimuthDeg + 90.f));
273 const float dy = std::sin(DEG2RAD(cam.cameraAzimuthDeg + 90.f));
274 const float d = key == GLFW_KEY_A ? -1.0f : 1.0f;
275 cam.cameraPointingX +=
276 d * dx * cam.cameraZoomDistance * SLIDE_VELOCITY;
277 cam.cameraPointingY +=
278 d * dy * cam.cameraZoomDistance * SLIDE_VELOCITY;
279 win->camera().setCameraParams(cam);
286 int cmd_rot = key == GLFW_KEY_LEFT ? -1 : 1;
289 const float dis = std::max(0.01
f, (cam.cameraZoomDistance));
291 cam.cameraPointingX + dis * cos(DEG2RAD(cam.cameraAzimuthDeg)) *
292 cos(DEG2RAD(cam.cameraElevationDeg));
294 cam.cameraPointingY + dis * sin(DEG2RAD(cam.cameraAzimuthDeg)) *
295 cos(DEG2RAD(cam.cameraElevationDeg));
296 float eye_z = cam.cameraPointingZ +
297 dis * sin(DEG2RAD(cam.cameraElevationDeg));
300 float A_AzimuthDeg = -SENSIBILITY_ROTATE * cmd_rot;
301 cam.cameraAzimuthDeg += A_AzimuthDeg;
303 float A_ElevationDeg = SENSIBILITY_ROTATE * cmd_elev;
304 cam.setElevationDeg(cam.cameraElevationDeg + A_ElevationDeg);
307 cam.cameraPointingX =
308 eye_x - dis * cos(DEG2RAD(cam.cameraAzimuthDeg)) *
309 cos(DEG2RAD(cam.cameraElevationDeg));
310 cam.cameraPointingY =
311 eye_y - dis * sin(DEG2RAD(cam.cameraAzimuthDeg)) *
312 cos(DEG2RAD(cam.cameraElevationDeg));
313 cam.cameraPointingZ =
314 eye_z - dis * sin(DEG2RAD(cam.cameraElevationDeg));
316 win->camera().setCameraParams(cam);
322 void camTravellingStop()
324 camTravellingCurrentTime.reset();
325 btnAnimate->setEnabled(
true);
326 btnAnimStop->setEnabled(
false);
329 void processCameraTravelling()
331 if (!camTravellingCurrentTime.has_value())
return;
332 double&
t = camTravellingCurrentTime.value();
335 const double t0 = mrpt::Clock::toDouble(camTravelling.begin()->first);
336 const double t1 = mrpt::Clock::toDouble(camTravelling.rbegin()->first);
337 slAnimProgress->setRange(std::make_pair<float>(t0, t1));
338 slAnimProgress->setValue(t);
347 const auto interpMethod =
348 cbTravellingInterp->selectedIndex() == 0
349 ? mrpt::poses::TInterpolatorMethod::imLinear2Neig
350 : mrpt::poses::TInterpolatorMethod::imSSLSLL;
351 camTravelling.setInterpolationMethod(interpMethod);
353 mrpt::math::TPose3D p;
355 camTravelling.interpolate(mrpt::Clock::fromDouble(t), p, valid);
358 win->camera().setCameraPointing(p.x, p.y, p.z);
359 win->camera().setAzimuthDegrees(mrpt::RAD2DEG(p.yaw));
360 win->camera().setElevationDegrees(mrpt::RAD2DEG(p.pitch));
361 win->camera().setZoomDistance(p.roll / TRAV_ZOOM2ROLL);
365 const double FPS = std::stod(edAnimFPS->value());
367 const double dt = 1.0 / FPS;
373 using namespace std::string_literals;
378 char appCfgFile[1024];
380 mrpt::config::CConfigFile appCfg(appCfgFile);
389 mrpt::gui::CDisplayWindowGUI_Params cp;
391 win = mrpt::gui::CDisplayWindowGUI::Create(
APP_NAME, 1024, 800, cp);
394 auto scene = mrpt::opengl::COpenGLScene::Create();
396 glGrid->setColor_u8(0xff, 0xff, 0xff, 0x10);
397 scene->insert(glGrid);
400 glMapCorner = mrpt::opengl::stock_objects::CornerXYZ(1.0
f);
401 glMapCorner->setName(
"map");
402 glMapCorner->enableShowName();
404 glTrajectory = mrpt::opengl::CSetOfObjects::Create();
406 glENUCorner = mrpt::opengl::stock_objects::CornerXYZ(2.0
f);
407 glENUCorner->setName(
"ENU");
408 glENUCorner->enableShowName();
409 scene->insert(glENUCorner);
411 scene->insert(glVizMap);
414 std::lock_guard<std::mutex> lck(win->background_scene_mtx);
415 win->background_scene = std::move(scene);
420 auto w = win->createManagedSubWindow(
"Map viewer");
421 w->setPosition({5, 25});
423 w->setLayout(
new nanogui::BoxLayout(
424 nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 5, 2));
425 w->setFixedWidth(350);
427 for (
size_t i = 0; i < lbMapStats.size(); i++)
429 auto& lb = lbMapStats[i];
433 auto pn = w->add<nanogui::Widget>();
434 pn->setLayout(
new nanogui::BoxLayout(
435 nanogui::Orientation::Horizontal,
436 nanogui::Alignment::Fill));
437 lb = pn->add<nanogui::TextBox>(
" ");
438 lb->setFixedWidth(300);
440 pn->add<nanogui::Button>(
"", ENTYPO_ICON_ARCHIVE);
441 btnLoad->setCallback(
446 const auto fil = nanogui::file_dialog(
447 {{
"mm",
"Metric maps (*.mm)"}},
false);
448 if (fil.empty())
return;
451 rebuildLayerCheckboxes();
452 win->performLayout();
455 catch (
const std::exception& e)
457 std::cerr << e.what() << std::endl;
461 else { lb = w->add<nanogui::TextBox>(
" "); }
464 lb->setAlignment(nanogui::TextBox::Alignment::Left);
465 lb->setEditable(
true);
469 w->add<nanogui::Label>(
" ");
471 auto tabWidget = w->add<nanogui::TabWidget>();
473 auto* tab1 = tabWidget->createTab(
"View");
474 tab1->setLayout(
new nanogui::GroupLayout());
476 auto* tab2 = tabWidget->createTab(
"Maps");
477 tab2->setLayout(
new nanogui::BoxLayout(
478 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
480 auto* tab3 = tabWidget->createTab(
"Travelling");
481 tab3->setLayout(
new nanogui::BoxLayout(
482 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
484 tabWidget->setActiveTab(0);
489 tab2->add<nanogui::Label>(
"Render options:");
492 auto pn = tab2->add<nanogui::Widget>();
493 pn->setLayout(
new nanogui::GridLayout(
494 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
496 lbPointSize = pn->add<nanogui::Label>(
"Point size");
499 slPointSize = pn->add<nanogui::Slider>();
500 slPointSize->setRange({1.0f, 10.0f});
501 slPointSize->setValue(2.0
f);
502 slPointSize->setCallback([&](
float) { rebuild_3d_view(); });
505 cbViewVoxelsAsPoints =
506 tab2->add<nanogui::CheckBox>(
"Render voxel maps as point clouds");
508 cbViewVoxelsAsPoints->setChecked(
false);
509 cbViewVoxelsAsPoints->setCallback([&](
bool) { rebuild_3d_view(); });
511 cbViewVoxelsFreeSpace =
512 tab2->add<nanogui::CheckBox>(
"Render free space of voxel maps");
514 cbViewVoxelsFreeSpace->setChecked(
false);
515 cbViewVoxelsFreeSpace->setCallback([&](
bool) { rebuild_3d_view(); });
517 cbColorizeMap = tab2->add<nanogui::CheckBox>(
"Recolorize map points");
519 cbColorizeMap->setChecked(
true);
520 cbColorizeMap->setCallback([&](
bool) { rebuild_3d_view(); });
522 cbKeepOriginalCloudColors =
523 tab2->add<nanogui::CheckBox>(
"Keep original cloud colors");
525 cbKeepOriginalCloudColors->setChecked(
false);
526 cbKeepOriginalCloudColors->setCallback([&](
bool)
527 { rebuild_3d_view(); });
529 tab2->add<nanogui::Label>(
" ");
531 auto pn = tab2->add<nanogui::Widget>();
532 pn->setLayout(
new nanogui::GridLayout(
533 nanogui::Orientation::Horizontal, 4, nanogui::Alignment::Fill));
534 pn->add<nanogui::Label>(
"Visible layers:");
536 auto* btnCheckAll = pn->add<nanogui::Button>(
"", ENTYPO_ICON_CHECK);
538 btnCheckAll->setCallback(
541 for (
auto& [name, cb] : cbLayersByName)
542 cb->setChecked(
true);
548 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CIRCLE_WITH_CROSS);
550 btnCheckNone->setCallback(
553 for (
auto& [name, cb] : cbLayersByName)
554 cb->setChecked(
false);
559 panelLayers = tab2->add<nanogui::Widget>();
560 panelLayers->setLayout(
new nanogui::BoxLayout(
561 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
563 rebuildLayerCheckboxes();
566 tab2->add<nanogui::Label>(
" ");
568 tab2->add<nanogui::Button>(
"Export marked layers...");
570 btnSave->setCallback([]() { onSaveLayers(); });
577 auto pn = tab1->add<nanogui::Widget>();
578 pn->setLayout(
new nanogui::GridLayout(
579 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
581 lbTrajThick = pn->add<nanogui::Label>(
"Trajectory thickness:");
584 slTrajectoryThickness = pn->add<nanogui::Slider>();
585 slTrajectoryThickness->setEnabled(trajectory.size() >= 2);
586 slTrajectoryThickness->setRange({std::log(0.005
f), std::log(2.0
f)});
587 slTrajectoryThickness->setValue(std::log(0.05
f));
588 slTrajectoryThickness->setCallback(
591 glTrajectory->clear();
597 auto pn = tab1->add<nanogui::Widget>();
598 pn->setLayout(
new nanogui::GridLayout(
599 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
602 pn->add<nanogui::Label>(
"Center depth clip plane:");
605 slMidDepthField = pn->add<nanogui::Slider>();
606 slMidDepthField->setRange({-2.0, 3.0});
607 slMidDepthField->setValue(1.0
f);
608 slMidDepthField->setCallback([&](
float) { rebuild_3d_view(); });
612 auto pn = tab1->add<nanogui::Widget>();
613 pn->setLayout(
new nanogui::GridLayout(
614 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
616 lbDepthFieldThickness =
617 pn->add<nanogui::Label>(
"Max-Min depth thickness:");
620 slThicknessDepthField = pn->add<nanogui::Slider>();
621 slThicknessDepthField->setRange({-2.0, 6.0});
622 slThicknessDepthField->setValue(3.0);
623 slThicknessDepthField->setCallback([&](
float)
624 { rebuild_3d_view(); });
627 auto pn = tab1->add<nanogui::Widget>();
628 pn->setLayout(
new nanogui::GridLayout(
629 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
631 lbCameraFOV = pn->add<nanogui::Label>(
"Camera FOV:");
633 slCameraFOV = pn->add<nanogui::Slider>();
634 slCameraFOV->setRange({20.0f, 170.0f});
635 slCameraFOV->setValue(90.0
f);
636 slCameraFOV->setCallback([&](
float) { rebuild_3d_view(); });
638 lbDepthFieldValues = tab1->add<nanogui::Label>(
" ");
642 auto pn = tab1->add<nanogui::Widget>();
643 pn->setLayout(
new nanogui::GridLayout(
644 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
646 cbViewOrtho = pn->add<nanogui::CheckBox>(
"Orthogonal view");
648 cbViewOrtho->setCallback([&](
bool) { rebuild_3d_view(); });
651 cbView2D = pn->add<nanogui::CheckBox>(
"Force 2D view");
653 cbView2D->setCallback([&](
bool) { rebuild_3d_view(); });
657 auto pn = tab1->add<nanogui::Widget>();
658 pn->setLayout(
new nanogui::GridLayout(
659 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
661 cbShowGroundGrid = pn->add<nanogui::CheckBox>(
"Show ground grid");
663 cbShowGroundGrid->setChecked(
true);
664 cbShowGroundGrid->setCallback([&](
bool) { rebuild_3d_view(); });
666 auto btnFitView = pn->add<nanogui::Button>(
"Fit view to map");
668 btnFitView->setCallback(
676 cbApplyGeoRef = tab1->add<nanogui::CheckBox>(
677 "Apply georeferenced pose (if available)");
679 cbApplyGeoRef->setCallback([&](
bool) { rebuild_3d_view(); });
684 tab3->add<nanogui::Label>(
"Define camera travelling paths");
687 auto pn = tab3->add<nanogui::Widget>();
688 pn->setLayout(
new nanogui::GridLayout(
689 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
691 auto lb = pn->add<nanogui::Label>(
"Keyframes:");
694 cbTravellingKeys = tab3->add<nanogui::ComboBox>();
697 rebuildCamTravellingCombo();
699 tab3->add<nanogui::Label>(
"");
701 nanogui::TextBox* edTime;
703 auto pn = tab3->add<nanogui::Widget>();
704 pn->setLayout(
new nanogui::GridLayout(
705 nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
707 pn->add<nanogui::Label>(
"New keyframe:")
710 edTime = pn->add<nanogui::TextBox>();
711 edTime->setAlignment(nanogui::TextBox::Alignment::Left);
712 edTime->setValue(
"0.0");
713 edTime->setEditable(
true);
714 edTime->setPlaceholder(
"Time for this keyframe [s]");
715 edTime->setFormat(
"[0-9\\.]*");
719 pn->add<nanogui::Button>(
"Add", ENTYPO_ICON_ADD_TO_LIST);
724 const auto p = mrpt::math::TPose3D(
725 win->camera().cameraParams().cameraPointingX,
726 win->camera().cameraParams().cameraPointingY,
727 win->camera().cameraParams().cameraPointingZ,
729 win->camera().cameraParams().cameraAzimuthDeg),
731 win->camera().cameraParams().cameraElevationDeg),
732 win->camera().cameraParams().cameraZoomDistance *
734 camTravelling.insert(
735 mrpt::Clock::fromDouble(std::stod(edTime->value())), p);
736 rebuildCamTravellingCombo();
739 std::to_string(std::stod(edTime->value()) + 1));
743 tab3->add<nanogui::Label>(
"");
746 auto pn = tab3->add<nanogui::Widget>();
747 pn->setLayout(
new nanogui::GridLayout(
748 nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
750 pn->add<nanogui::Label>(
"Playback:");
752 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CONTROLLER_PLAY);
754 btnAnimate->setCallback(
757 if (camTravelling.empty())
return;
758 camTravellingCurrentTime.emplace(
759 mrpt::Clock::toDouble(camTravelling.begin()->first));
760 btnAnimate->setEnabled(
false);
761 btnAnimStop->setEnabled(
true);
764 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CIRCLE_WITH_CROSS);
766 btnAnimStop->setEnabled(
false);
767 btnAnimStop->setCallback([]() { camTravellingStop(); });
771 auto pn = tab3->add<nanogui::Widget>();
772 pn->setLayout(
new nanogui::GridLayout(
773 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
775 auto lb = pn->add<nanogui::Label>(
"Animation FPS:");
778 edAnimFPS = pn->add<nanogui::TextBox>(
"30.0");
780 edAnimFPS->setFormat(
"[0-9\\.]*");
781 edAnimFPS->setEditable(
true);
784 auto pn = tab3->add<nanogui::Widget>();
785 pn->setLayout(
new nanogui::GridLayout(
786 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
788 auto lb = pn->add<nanogui::Label>(
"Interpolation:");
791 cbTravellingInterp = pn->add<nanogui::ComboBox>();
793 cbTravellingInterp->setItems({
"Linear",
"Spline"});
794 cbTravellingInterp->setSelectedIndex(0);
797 slAnimProgress = tab3->add<nanogui::Slider>();
798 slAnimProgress->setEnabled(
false);
801 lbMousePos = w->add<nanogui::Label>(
"Mouse pointing to:");
803 lbCameraPointing = w->add<nanogui::Label>(
"Camera looking at:");
806 w->add<nanogui::Button>(
"Quit", ENTYPO_ICON_ARROW_BOLD_LEFT)
807 ->setCallback([]() { win->setVisible(
false); });
809 win->setKeyboardCallback(
810 [&](
int key, [[maybe_unused]]
int scancode,
int action,
811 [[maybe_unused]]
int modifiers)
813 if (action != GLFW_PRESS && action != GLFW_REPEAT)
return false;
815 onKeyboardAction(key);
824 win->performLayout();
825 win->camera().setCameraPointing(8.0
f, .0
f, .0
f);
826 win->camera().setAzimuthDegrees(110.0
f);
827 win->camera().setElevationDegrees(15.0
f);
828 win->camera().setZoomDistance(50.0
f);
831 #define LOAD_CB_STATE(CB_NAME__) do_cb(CB_NAME__, #CB_NAME__)
832 #define SAVE_CB_STATE(CB_NAME__) \
833 appCfg.write("", #CB_NAME__, CB_NAME__->checked())
835 #define LOAD_SL_STATE(SL_NAME__) do_sl(SL_NAME__, #SL_NAME__)
836 #define SAVE_SL_STATE(SL_NAME__) \
837 appCfg.write("", #SL_NAME__, SL_NAME__->value())
839 auto load_UI_state_from_user_config = [&]()
841 auto do_cb = [&](nanogui::CheckBox* cb,
const std::string& name)
842 { cb->setChecked(appCfg.read_bool(
"", name, cb->checked())); };
843 auto do_sl = [&](nanogui::Slider* sl,
const std::string& name)
844 { sl->setValue(appCfg.read_float(
"", name, sl->value())); };
846 LOAD_CB_STATE(cbApplyGeoRef);
847 LOAD_CB_STATE(cbViewOrtho);
848 LOAD_CB_STATE(cbView2D);
849 LOAD_CB_STATE(cbViewVoxelsAsPoints);
850 LOAD_CB_STATE(cbViewVoxelsFreeSpace);
851 LOAD_CB_STATE(cbColorizeMap);
852 LOAD_CB_STATE(cbKeepOriginalCloudColors);
853 LOAD_CB_STATE(cbShowGroundGrid);
855 LOAD_SL_STATE(slPointSize);
856 LOAD_SL_STATE(slTrajectoryThickness);
857 LOAD_SL_STATE(slMidDepthField);
858 LOAD_SL_STATE(slThicknessDepthField);
859 LOAD_SL_STATE(slCameraFOV);
861 win->camera().setCameraPointing(
862 appCfg.read_float(
"",
"cam_x", win->camera().getCameraPointingX()),
863 appCfg.read_float(
"",
"cam_y", win->camera().getCameraPointingY()),
864 appCfg.read_float(
"",
"cam_z", win->camera().getCameraPointingZ()));
865 win->camera().setAzimuthDegrees(
866 appCfg.read_float(
"",
"cam_az", win->camera().getAzimuthDegrees()));
867 win->camera().setElevationDegrees(appCfg.read_float(
868 "",
"cam_el", win->camera().getElevationDegrees()));
869 win->camera().setZoomDistance(
870 appCfg.read_float(
"",
"cam_d", win->camera().getZoomDistance()));
872 auto save_UI_state_to_user_config = [&]()
874 SAVE_CB_STATE(cbApplyGeoRef);
875 SAVE_CB_STATE(cbViewOrtho);
876 SAVE_CB_STATE(cbView2D);
877 SAVE_CB_STATE(cbViewVoxelsAsPoints);
878 SAVE_CB_STATE(cbViewVoxelsFreeSpace);
879 SAVE_CB_STATE(cbColorizeMap);
880 SAVE_CB_STATE(cbKeepOriginalCloudColors);
881 SAVE_CB_STATE(cbShowGroundGrid);
883 SAVE_SL_STATE(slPointSize);
884 SAVE_SL_STATE(slTrajectoryThickness);
885 SAVE_SL_STATE(slMidDepthField);
886 SAVE_SL_STATE(slThicknessDepthField);
887 SAVE_SL_STATE(slCameraFOV);
889 appCfg.write(
"",
"cam_x", win->camera().getCameraPointingX());
890 appCfg.write(
"",
"cam_y", win->camera().getCameraPointingY());
891 appCfg.write(
"",
"cam_z", win->camera().getCameraPointingZ());
892 appCfg.write(
"",
"cam_az", win->camera().getAzimuthDegrees());
893 appCfg.write(
"",
"cam_el", win->camera().getElevationDegrees());
894 appCfg.write(
"",
"cam_d", win->camera().getZoomDistance());
898 load_UI_state_from_user_config();
906 win->setVisible(
true);
908 win->addLoopCallback(
911 updateMouseCoordinates();
912 updateCameraLookCoordinates();
913 observeViewOptions();
914 updateMiniCornerView();
915 processCameraTravelling();
918 nanogui::mainloop(1000 , 25 );
923 save_UI_state_to_user_config();
931 void rebuild_3d_view()
933 using namespace std::string_literals;
935 lbMapStats[0]->setValue(theMapFileName);
941 std::optional<mrpt::math::TBoundingBoxf> mapBbox;
947 for (
const auto& [lyName, cb] : cbLayersByName)
950 cb->setCaption(lyName);
951 if (
auto itL = theMap.
layers.find(lyName); itL != theMap.
layers.end())
953 if (
auto pc = std::dynamic_pointer_cast<mrpt::maps::CPointsMap>(
959 mrpt::system::unitsFormat(
960 static_cast<double>(pc->size()), 2,
false) +
961 " points"s +
" | class="s +
962 pc->GetRuntimeClass()->className);
964 const auto bb = pc->boundingBox();
965 if (!mapBbox.has_value())
967 else { mapBbox = mapBbox->unionWith(bb); }
972 lyName +
" | class="s +
973 itL->second->GetRuntimeClass()->className);
978 if (!cb->checked())
continue;
982 rpL.pointSize = slPointSize->value();
983 rpL.render_voxelmaps_as_points = cbViewVoxelsAsPoints->checked();
984 rpL.render_voxelmaps_free_space = cbViewVoxelsFreeSpace->checked();
986 lbPointSize->setCaption(
"Point size: " + std::to_string(rpL.pointSize));
988 if (cbColorizeMap->checked())
990 auto& cm = rpL.colorMode.emplace();
991 cm.colorMap = mrpt::img::TColormap::cmJET;
994 if (cbKeepOriginalCloudColors->checked())
996 auto& cm = rpL.colorMode.emplace();
997 cm.keep_original_cloud_color =
true;
1003 rp.color = mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1006 static std::optional<mp2p_icp::render_params_t> prevRenderParams;
1008 if (!prevRenderParams.has_value() || prevRenderParams.value() != rpMap)
1010 prevRenderParams = rpMap;
1017 mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1019 glVizMap->insert(glPts);
1020 glVizMap->insert(glMapCorner);
1021 glVizMap->insert(glTrajectory);
1024 if (cbApplyGeoRef->checked() && theMap.
georeferencing.has_value())
1028 glENUCorner->setVisibility(
true);
1032 glVizMap->setPose(mrpt::poses::CPose3D::Identity());
1033 glGrid->setPose(mrpt::poses::CPose3D::Identity());
1034 glENUCorner->setVisibility(
false);
1040 glGrid->setPlaneLimits(
1041 mapBbox->min.x, mapBbox->max.x, mapBbox->min.y, mapBbox->max.y);
1043 glGrid->setVisibility(cbShowGroundGrid->checked());
1046 if (mapBbox && doFitView)
1048 const auto midPt = (mapBbox->min + mapBbox->max) * 0.5;
1049 const auto mapLen = (mapBbox->max - mapBbox->min).norm();
1051 win->camera().setCameraPointing(midPt.x, midPt.y, midPt.z);
1052 win->camera().setZoomDistance(mapLen);
1057 if (glTrajectory->empty() && trajectory.size() >= 2)
1059 const float trajCylRadius = std::exp(slTrajectoryThickness->value());
1060 lbTrajThick->setCaption(
1061 "Traj. thickness: " + std::to_string(trajCylRadius));
1063 std::optional<mrpt::math::TPose3D> prevPose;
1064 for (
const auto& [t, p] : trajectory)
1068 const auto& p0 = prevPose.value();
1070 auto glSegment = mrpt::opengl::CArrow::Create();
1071 glSegment->setArrowEnds(p0.translation(), p.translation());
1072 glSegment->setHeadRatio(.0);
1073 glSegment->setLargeRadius(trajCylRadius);
1074 glSegment->setSmallRadius(trajCylRadius);
1075 glSegment->setColor_u8(0x30, 0x30, 0x30, 0xff);
1077 glTrajectory->insert(glSegment);
1085 auto gl_view = win->background_scene->createViewport(
"small-view");
1087 gl_view->setViewportPosition(0, 0, 0.1, 0.1 * 16.0 / 9.0);
1088 gl_view->setTransparent(
true);
1090 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"X");
1091 obj->setLocation(1.1, 0, 0);
1092 gl_view->insert(obj);
1095 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"Y");
1096 obj->setLocation(0, 1.1, 0);
1097 gl_view->insert(obj);
1100 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"Z");
1101 obj->setLocation(0, 0, 1.1);
1102 gl_view->insert(obj);
1104 gl_view->insert(mrpt::opengl::stock_objects::CornerXYZ());
1109 std::lock_guard<std::mutex> lck(win->background_scene_mtx);
1110 win->camera().setCameraProjective(
1111 !cbViewOrtho->checked() && !cbView2D->checked());
1114 const auto depthFieldMid =
std::pow(10.0, slMidDepthField->value());
1115 const auto depthFieldThickness =
1116 std::pow(10.0, slThicknessDepthField->value());
1118 const auto clipNear =
1119 std::max(1e-2, depthFieldMid - 0.5 * depthFieldThickness);
1120 const auto clipFar = depthFieldMid + 0.5 * depthFieldThickness;
1122 const float cameraFOV = slCameraFOV->value();
1123 win->camera().setCameraFOV(cameraFOV);
1124 win->camera().setMaximumZoom(std::max<double>(1000, 3.0 * clipFar));
1126 lbDepthFieldValues->setCaption(
1127 mrpt::format(
"Depth field: near=%g far=%g", clipNear, clipFar));
1128 lbDepthFieldMid->setCaption(
1129 mrpt::format(
"Frustrum center: %.03g", depthFieldMid));
1130 lbDepthFieldThickness->setCaption(
1131 mrpt::format(
"Frustum thickness: %.03g", depthFieldThickness));
1133 lbDepthFieldValues->setCaption(
1134 mrpt::format(
"Frustum: near=%.02g far=%.02g", clipNear, clipFar));
1136 lbCameraFOV->setCaption(
1137 mrpt::format(
"Camera FOV: %.02f deg", cameraFOV));
1139 win->background_scene->getViewport()->setViewportClipDistances(
1147 nanogui::file_dialog({{
"txt",
"(*.txt)"}},
true );
1148 if (outFile.empty())
return;
1150 for (
const auto& [lyName, cb] : cbLayersByName)
1152 if (
auto itL = theMap.
layers.find(lyName); itL != theMap.
layers.end())
1154 itL->second->saveMetricMapRepresentationToFile(outFile);
1159 #else // MRPT_HAS_NANOGUI
1163 "This application requires a version of MRPT built with nanogui "
1167 #endif // MRPT_HAS_NANOGUI
1174 if (!
cmd.parse(argc, argv))
return 1;
1181 std::cout <<
"Loading plugin(s): " << plugins << std::endl;
1182 if (!mrpt::system::loadPluginModules(plugins, errMsg))
1184 std::cerr << errMsg << std::endl;
1193 bool trajectoryReadOk =
1195 ASSERT_(trajectoryReadOk);
1196 std::cout <<
"Read trajectory with " << trajectory.size()
1203 catch (std::exception& e)
1205 std::cerr <<
"Exit due to exception:\n"
1206 << mrpt::exception_to_str(e) << std::endl;