ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
GuiVisualizer.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <CloudViewerConfig.h>
11 #include <FileSystem.h>
12 #include <ecvMesh.h>
13 
14 #include <random>
15 
21 #include "cloudViewer/io/ImageIO.h"
22 #include "cloudViewer/io/ModelIO.h"
54 
55 #define LOAD_IN_NEW_WINDOW 0
56 
57 namespace cloudViewer {
58 namespace visualization {
59 
60 namespace {
61 
62 std::shared_ptr<gui::Dialog> CreateAboutDialog(gui::Window *window) {
63  auto &theme = window->GetTheme();
64  auto dlg = std::make_shared<gui::Dialog>("About");
65 
66  auto title = std::make_shared<gui::Label>(
67  (std::string("CloudViewer ") + CLOUDVIEWER_VERSION).c_str());
68  auto text = std::make_shared<gui::Label>(
69  "The MIT License (MIT)\n"
70  "Copyright (c) 2018-2025 asher-1.github.io\n\n"
71 
72  "Permission is hereby granted, free of charge, to any person "
73  "obtaining a copy of this software and associated documentation "
74  "files (the \"Software\"), to deal in the Software without "
75  "restriction, including without limitation the rights to use, "
76  "copy, modify, merge, publish, distribute, sublicense, and/or "
77  "sell copies of the Software, and to permit persons to whom "
78  "the Software is furnished to do so, subject to the following "
79  "conditions:\n\n"
80 
81  "The above copyright notice and this permission notice shall be "
82  "included in all copies or substantial portions of the "
83  "Software.\n\n"
84 
85  "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, "
86  "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES "
87  "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND "
88  "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT "
89  "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, "
90  "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING "
91  "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR "
92  "OTHER DEALINGS IN THE SOFTWARE.");
93  auto ok = std::make_shared<gui::Button>("OK");
94  ok->SetOnClicked([window]() { window->CloseDialog(); });
95 
96  gui::Margins margins(theme.font_size);
97  auto layout = std::make_shared<gui::Vert>(0, margins);
98  layout->AddChild(gui::Horiz::MakeCentered(title));
99  layout->AddFixed(theme.font_size);
100  auto v = std::make_shared<gui::ScrollableVert>(0);
101  v->AddChild(text);
102  layout->AddChild(v);
103  layout->AddFixed(theme.font_size);
104  layout->AddChild(gui::Horiz::MakeCentered(ok));
105  dlg->AddChild(layout);
106 
107  return dlg;
108 }
109 
110 std::shared_ptr<gui::VGrid> CreateHelpDisplay(gui::Window *window) {
111  auto &theme = window->GetTheme();
112 
113  gui::Margins margins(theme.font_size);
114  auto layout = std::make_shared<gui::VGrid>(2, 0, margins);
115  layout->SetBackgroundColor(gui::Color(0, 0, 0, 0.5));
116 
117  auto AddLabel = [layout](const char *text) {
118  auto label = std::make_shared<gui::Label>(text);
119  label->SetTextColor(gui::Color(1, 1, 1));
120  layout->AddChild(label);
121  };
122  auto AddRow = [layout, &AddLabel](const char *left, const char *right) {
123  AddLabel(left);
124  AddLabel(right);
125  };
126 
127  AddRow("Arcball mode", " ");
128  AddRow("Left-drag", "Rotate camera");
129  AddRow("Shift + left-drag", "Forward/backward");
130 
131 #if defined(__APPLE__)
132  AddLabel("Cmd + left-drag");
133 #else
134  AddLabel("Ctrl + left-drag");
135 #endif // __APPLE__
136  AddLabel("Pan camera");
137 
138 #if defined(__APPLE__)
139  AddLabel("Opt + left-drag (up/down) ");
140 #else
141  AddLabel("Win + left-drag (up/down) ");
142 #endif // __APPLE__
143  AddLabel("Rotate around forward axis");
144 
145  // GNOME3 uses Win/Meta as a shortcut to move windows around, so we
146  // need another way to rotate around the forward axis.
147  AddLabel("Ctrl + Shift + left-drag");
148  AddLabel("Rotate around forward axis");
149 
150 #if defined(__APPLE__)
151  AddLabel("Ctrl + left-drag");
152 #else
153  AddLabel("Alt + left-drag");
154 #endif // __APPLE__
155  AddLabel("Rotate directional light");
156 
157  AddRow("Right-drag", "Pan camera");
158  AddRow("Middle-drag", "Rotate directional light");
159  AddRow("Wheel", "Forward/backward");
160  AddRow("Shift + Wheel", "Change field of view");
161  AddRow("", "");
162 
163  AddRow("Fly mode", " ");
164  AddRow("Left-drag", "Rotate camera");
165 #if defined(__APPLE__)
166  AddLabel("Opt + left-drag");
167 #else
168  AddLabel("Win + left-drag");
169 #endif // __APPLE__
170  AddLabel("Rotate around forward axis");
171  AddRow("W", "Forward");
172  AddRow("S", "Backward");
173  AddRow("A", "Step left");
174  AddRow("D", "Step right");
175  AddRow("Q", "Step up");
176  AddRow("Z", "Step down");
177  AddRow("E", "Roll left");
178  AddRow("R", "Roll right");
179  AddRow("Up", "Look up");
180  AddRow("Down", "Look down");
181  AddRow("Left", "Look left");
182  AddRow("Right", "Look right");
183 
184  return layout;
185 }
186 
187 std::shared_ptr<gui::VGrid> CreateCameraDisplay(gui::Window *window) {
188  auto &theme = window->GetTheme();
189 
190  gui::Margins margins(theme.font_size);
191  auto layout = std::make_shared<gui::VGrid>(2, 0, margins);
192  layout->SetBackgroundColor(gui::Color(0, 0, 0, 0.5));
193 
194  auto AddLabel = [layout](const char *text) {
195  auto label = std::make_shared<gui::Label>(text);
196  label->SetTextColor(gui::Color(1, 1, 1));
197  layout->AddChild(label);
198  };
199  auto AddRow = [layout, &AddLabel](const char *left, const char *right) {
200  AddLabel(left);
201  AddLabel(right);
202  };
203 
204  AddRow("Position:", "[0 0 0]");
205  AddRow("Forward:", "[0 0 0]");
206  AddRow("Left:", "[0 0 0]");
207  AddRow("Up:", "[0 0 0]");
208 
209  return layout;
210 }
211 
212 std::shared_ptr<gui::Dialog> CreateContactDialog(gui::Window *window) {
213  auto &theme = window->GetTheme();
214  auto em = theme.font_size;
215  auto dlg = std::make_shared<gui::Dialog>("Contact Us");
216 
217  auto title = std::make_shared<gui::Label>("Contact Us");
218  auto left_col = std::make_shared<gui::Label>(
219  "Web site:\n"
220  "Code:\n"
221  "Mailing list:\n"
222  "Discord channel:");
223  auto right_col = std::make_shared<gui::Label>(
224  "https://Asher-1.github.io\n"
225  "https://github.com/Asher-1/ACloudViewer\n"
226  "https://Asher-1.github.io/docs/\n"
227  "https://discord.gg/D35BGvn");
228  auto ok = std::make_shared<gui::Button>("OK");
229  ok->SetOnClicked([window]() { window->CloseDialog(); });
230 
231  gui::Margins margins(em);
232  auto layout = std::make_shared<gui::Vert>(0, margins);
233  layout->AddChild(gui::Horiz::MakeCentered(title));
234  layout->AddFixed(em);
235 
236  auto columns = std::make_shared<gui::Horiz>(em, gui::Margins());
237  columns->AddChild(left_col);
238  columns->AddChild(right_col);
239  layout->AddChild(columns);
240 
241  layout->AddFixed(em);
242  layout->AddChild(gui::Horiz::MakeCentered(ok));
243  dlg->AddChild(layout);
244 
245  return dlg;
246 }
247 
248 bool ColorArrayIsUniform(const std::vector<Eigen::Vector3d> &colors) {
249  static const double e = 1.0 / 255.0;
250  static const double SQ_EPSILON = Eigen::Vector3d(e, e, e).squaredNorm();
251  const auto &color = colors[0];
252 
253  for (const auto &c : colors) {
254  if ((color - c).squaredNorm() > SQ_EPSILON) {
255  return false;
256  }
257  }
258 
259  return true;
260 }
261 
262 bool PointCloudHasUniformColor(const geometry::PointCloud &pcd) {
263  if (!pcd.hasColors()) {
264  return true;
265  }
266  return ColorArrayIsUniform(pcd.getEigenColors());
267 };
268 
269 //----
270 class DrawTimeLabel : public gui::Label {
271  using Super = Label;
272 
273 public:
274  DrawTimeLabel(gui::Window *w) : Label("0.0 ms") { window_ = w; }
275 
276  gui::Size CalcPreferredSize(const gui::LayoutContext &context,
277  const Constraints &constraints) const override {
278  auto h = Super::CalcPreferredSize(context, constraints).height;
279  return gui::Size(context.theme.font_size * 5, h);
280  }
281 
282  DrawResult Draw(const gui::DrawContext &context) override {
283  char text[64];
284  // double ms = window_->GetLastFrameTimeSeconds() * 1000.0;
285  double ms = 0.0;
286  snprintf(text, sizeof(text) - 1, "%.1f ms", ms);
287  SetText(text);
288 
289  return Super::Draw(context);
290  }
291 
292 private:
293  gui::Window *window_;
294 };
295 
296 } // namespace
297 
298 const std::string MODEL_NAME = "__model__";
299 const std::string INSPECT_MODEL_NAME = "__inspect_model__";
300 const std::string WIREFRAME_NAME = "__wireframe_model__";
301 
302 enum MenuId {
311  HELP_DEBUG
312 };
313 
316 
317  std::shared_ptr<gui::SceneWidget> scene_wgt_;
318  std::shared_ptr<gui::VGrid> help_keys_;
319  std::shared_ptr<gui::VGrid> help_camera_;
320  std::shared_ptr<io::rpc::ZMQReceiver> receiver_;
321  std::shared_ptr<MessageProcessor> message_processor_;
322 
323  struct Settings {
327 
329  std::shared_ptr<gui::Vert> wgt_base;
330  std::shared_ptr<gui::Button> wgt_mouse_arcball;
331  std::shared_ptr<gui::Button> wgt_mouse_fly;
332  std::shared_ptr<gui::Button> wgt_mouse_model;
333  std::shared_ptr<gui::Button> wgt_mouse_sun;
334  std::shared_ptr<gui::Button> wgt_mouse_ibl;
335  std::shared_ptr<GuiSettingsView> view_;
337 
340  std::shared_ptr<geometry::LineSet> wireframe_model_;
341  std::shared_ptr<geometry::PointCloud> loaded_pcd_;
343  std::shared_ptr<gui::Menu> app_menu_;
344 
345  bool sun_follows_camera_ = false;
346  bool basic_mode_enabled_ = false;
347  bool wireframe_enabled_ = false;
348 
350  const std::string &resource_path) {
351  settings_.lit_material_.shader = "defaultLit";
352  settings_.unlit_material_.shader = "defaultUnlit";
353 
354  auto &defaults = settings_.model_.GetCurrentMaterials();
355 
356  UpdateMaterials(renderer, defaults);
357  }
358 
360  settings_.view_->ShowFileMaterialEntry(false);
361 
363  settings_.view_->EnableEstimateNormals(false);
364  // model's OnChanged callback will get called (if set), which will
365  // update everything.
366  }
367 
368  bool SetIBL(rendering::Renderer &renderer, const std::string &path) {
369  auto *render_scene = scene_wgt_->GetScene()->GetScene();
370  std::string ibl_name(path);
371  if (ibl_name.empty()) {
372  ibl_name =
373  std::string(
374  gui::Application::GetInstance().GetResourcePath()) +
376  }
377  if (ibl_name.find("_ibl.ktx") != std::string::npos) {
378  ibl_name = ibl_name.substr(0, ibl_name.size() - 8);
379  }
380  render_scene->SetIndirectLight(ibl_name);
381  float intensity = render_scene->GetIndirectLightIntensity();
382  render_scene->SetIndirectLightIntensity(intensity);
383  scene_wgt_->ForceRedraw();
384 
385  return true;
386  }
387 
390  using Controls = gui::SceneWidget::Controls;
391  scene_wgt_->SetViewControls(mode);
392  window.SetFocusWidget(scene_wgt_.get());
393  settings_.wgt_mouse_arcball->SetOn(mode == Controls::ROTATE_CAMERA);
394  settings_.wgt_mouse_fly->SetOn(mode == Controls::FLY);
395  settings_.wgt_mouse_model->SetOn(mode == Controls::ROTATE_MODEL);
396  settings_.wgt_mouse_sun->SetOn(mode == Controls::ROTATE_SUN);
397  settings_.wgt_mouse_ibl->SetOn(mode == Controls::ROTATE_IBL);
398  }
399 
401  // Set parameters for 'simple' rendering
402  basic_mat.base_color = {1.f, 1.f, 1.f, 1.f};
403  basic_mat.base_metallic = 0.f;
404  basic_mat.base_roughness = 0.5f;
405  basic_mat.base_reflectance = 0.8f;
406  basic_mat.base_clearcoat = 0.f;
407  basic_mat.base_anisotropy = 0.f;
408  basic_mat.albedo_img.reset();
409  basic_mat.normal_img.reset();
410  basic_mat.ao_img.reset();
411  basic_mat.metallic_img.reset();
412  basic_mat.roughness_img.reset();
413  basic_mat.reflectance_img.reset();
414  basic_mat.sRGB_color = false;
415  basic_mat.sRGB_vertex_color = false;
416  }
417 
418  void SetBasicModeGeometry(bool enable) {
419  auto o3dscene = scene_wgt_->GetScene();
420 
421  // Only need to modify TriangleMesh - basic mode for point clouds
422  // requires only change to their materials which are handled elsewhere
423  if (loaded_model_.meshes_.size() > 0) {
424  if (enable) {
425  if (basic_model_.meshes_.size() == 0) {
426  for (auto &mat : loaded_model_.materials_) {
429  basic_model_.materials_.emplace_back(m);
430  }
431  for (auto &mi : loaded_model_.meshes_) {
432  auto new_mesh = mi.mesh->cloneMesh();
433  if (!new_mesh->HasTriangleNormals()) {
434  new_mesh->ComputeTriangleNormals();
435  }
436  basic_model_.meshes_.push_back(
437  {std::shared_ptr<ccMesh>(new_mesh),
438  mi.mesh_name, mi.material_idx});
439  }
440  o3dscene->AddModel(INSPECT_MODEL_NAME, basic_model_);
441  }
442  o3dscene->ShowGeometry(INSPECT_MODEL_NAME, true);
443  o3dscene->ShowGeometry(MODEL_NAME, false);
444  } else {
445  o3dscene->ShowGeometry(INSPECT_MODEL_NAME, false);
446  o3dscene->ShowGeometry(MODEL_NAME, true);
447  }
448  }
449  }
450 
451  void SetBasicMode(bool enable) {
452  auto o3dscene = scene_wgt_->GetScene();
453  auto view = o3dscene->GetView();
454  auto low_scene = o3dscene->GetScene();
455 
456  // Set lighting environment for inspection
457  if (enable) {
458  // Set lighting environment for inspection
459  o3dscene->SetBackground({1.f, 1.f, 1.f, 1.f});
460  low_scene->ShowSkybox(false);
461  view->SetShadowing(false, rendering::View::ShadowType::kPCF);
462  view->SetPostProcessing(false);
463  } else {
464  view->SetPostProcessing(true);
465  view->SetShadowing(true, rendering::View::ShadowType::kPCF);
466  }
467 
468  // Update geometry for basic mode
469  SetBasicModeGeometry(enable);
470  }
471 
472  void UpdateFromModel(rendering::Renderer &renderer, bool material_changed) {
473  auto o3dscene = scene_wgt_->GetScene();
474 
476  o3dscene->ShowSkybox(true);
477  } else {
478  o3dscene->ShowSkybox(false);
479  }
480 
481  o3dscene->ShowAxes(settings_.model_.GetShowAxes());
482  o3dscene->ShowGroundPlane(settings_.model_.GetShowGround(),
484 
485  // Does user want Point Cloud normals estimated?
487  RunNormalEstimation();
488  }
489 
490  // o3dscene->ShowGeometry(WIREFRAME_NAME, true);
494  // create wireframe line set
495  if (!wireframe_model_) {
496  wireframe_model_ = std::make_shared<geometry::LineSet>();
497  for (const auto &mi : loaded_model_.meshes_) {
499  *mi.mesh);
500  *wireframe_model_ += *lines;
501  }
502  }
503  // Add to scene
504  rendering::MaterialRecord wireframe_mat;
505  wireframe_mat.shader = "unlitLine";
506  wireframe_mat.line_width = 2.f;
507  wireframe_mat.base_color = {0.f, 0.3f, 1.f, 1.f};
508  wireframe_mat.emissive_color = {10000.f, 10000.f, 10000.f, 1.f};
509  o3dscene->AddGeometry(WIREFRAME_NAME, wireframe_model_.get(),
510  wireframe_mat);
511  // o3dscene->ShowGeometry(WIREFRAME_NAME, true);
512  o3dscene->ShowGeometry(MODEL_NAME, false);
513  o3dscene->GetView()->SetWireframe(true);
514  o3dscene->SetBackground({0.1f, 0.1f, 0.1f, 1.f});
515  } else {
516  o3dscene->RemoveGeometry(WIREFRAME_NAME);
517  o3dscene->ShowGeometry(MODEL_NAME, true);
518  o3dscene->GetView()->SetWireframe(false);
519  auto bcolor = settings_.model_.GetBackgroundColor();
520  o3dscene->SetBackground(
521  {bcolor.x(), bcolor.y(), bcolor.z(), 1.f});
522  }
523  }
524 
528  }
529 
530  UpdateLighting(renderer, settings_.model_.GetLighting());
531 
532  // Make sure scene redraws once changes have been applied
533  scene_wgt_->ForceRedraw();
534 
535  // Bail early if there were no material property changes
536  if (!material_changed) return;
537 
538  auto &current_materials = settings_.model_.GetCurrentMaterials();
540  GuiSettingsModel::MaterialType::LIT &&
541  current_materials.lit_name ==
544  o3dscene->UpdateModelMaterial(MODEL_NAME, loaded_model_);
545  } else {
546  UpdateMaterials(renderer, current_materials);
547  UpdateSceneMaterial();
548  }
549 
550  auto *view = scene_wgt_->GetRenderView();
551  switch (settings_.model_.GetMaterialType()) {
552  case GuiSettingsModel::MaterialType::LIT: {
553  view->SetMode(rendering::View::Mode::Color);
554  break;
555  }
556  case GuiSettingsModel::MaterialType::UNLIT: {
557  view->SetMode(rendering::View::Mode::Color);
558  break;
559  }
560  case GuiSettingsModel::MaterialType::NORMAL_MAP:
561  view->SetMode(rendering::View::Mode::Normals);
562  break;
563  case GuiSettingsModel::MaterialType::DEPTH:
564  view->SetMode(rendering::View::Mode::Depth);
565  break;
566  }
567 
568  // Make sure scene redraws once material changes have been applied
569  scene_wgt_->ForceRedraw();
570  }
571 
572 private:
573  void UpdateLighting(rendering::Renderer &renderer,
574  const GuiSettingsModel::LightingProfile &lighting) {
575  auto scene = scene_wgt_->GetScene();
576  auto *render_scene = scene->GetScene();
577  if (lighting.use_default_ibl &&
579  this->SetIBL(renderer, "");
580  }
581 
584  if (sun_follows_camera_) {
585  scene_wgt_->SetOnCameraChanged([this](rendering::Camera *cam) {
586  auto render_scene = scene_wgt_->GetScene()->GetScene();
587  render_scene->SetSunLightDirection(cam->GetForwardVector());
588  });
589  render_scene->SetSunLightDirection(
590  scene->GetCamera()->GetForwardVector());
591  settings_.wgt_mouse_sun->SetEnabled(false);
592  scene_wgt_->SetSunInteractorEnabled(false);
593  } else {
594  scene_wgt_->SetOnCameraChanged(
595  std::function<void(rendering::Camera *)>());
596  settings_.wgt_mouse_sun->SetEnabled(true);
597  scene_wgt_->SetSunInteractorEnabled(true);
598  }
599  }
600 
601  render_scene->EnableIndirectLight(lighting.ibl_enabled);
602  render_scene->SetIndirectLightIntensity(float(lighting.ibl_intensity));
603  render_scene->SetIndirectLightRotation(lighting.ibl_rotation);
604  render_scene->SetSunLightColor(lighting.sun_color);
605  render_scene->SetSunLightIntensity(float(lighting.sun_intensity));
606  if (!sun_follows_camera_) {
607  render_scene->SetSunLightDirection(lighting.sun_dir);
608  } else {
609  render_scene->SetSunLightDirection(
610  scene->GetCamera()->GetForwardVector());
611  }
612  render_scene->EnableSunLight(lighting.sun_enabled);
613  }
614 
615  void RunNormalEstimation() {
616  if (loaded_pcd_) {
618  visualizer_, [this]() {
619  auto &theme = visualizer_->GetTheme();
620  auto loading_dlg =
621  std::make_shared<gui::Dialog>("Loading");
622  auto vert = std::make_shared<gui::Vert>(
623  0, gui::Margins(theme.font_size));
624  auto loading_text = std::string(
625  "Estimating normals. Be patient. This may take "
626  "a while. ");
627  vert->AddChild(std::make_shared<gui::Label>(
628  loading_text.c_str()));
629  loading_dlg->AddChild(vert);
630  visualizer_->ShowDialog(loading_dlg);
631  });
632 
634  loaded_pcd_->EstimateNormals();
635  loaded_pcd_->NormalizeNormals();
636 
638  visualizer_, [this]() {
639  auto scene3d = scene_wgt_->GetScene();
640  scene3d->ClearGeometry();
641  rendering::MaterialRecord mat;
642  scene3d->AddGeometry(MODEL_NAME, loaded_pcd_.get(),
643  mat);
644  UpdateSceneMaterial();
645  });
647  visualizer_, [this]() { visualizer_->CloseDialog(); });
648  });
649  }
650  }
651 
652  void UpdateSceneMaterial() {
653  switch (settings_.model_.GetMaterialType()) {
654  case GuiSettingsModel::MaterialType::LIT:
655  if (basic_mode_enabled_) {
656  rendering::MaterialRecord basic_mat(
658  ModifyMaterialForBasicMode(basic_mat);
659  scene_wgt_->GetScene()->UpdateMaterial(basic_mat);
660  } else {
661  scene_wgt_->GetScene()->UpdateMaterial(
663  }
664  break;
665  case GuiSettingsModel::MaterialType::UNLIT:
666  if (basic_mode_enabled_) {
667  rendering::MaterialRecord basic_mat(
669  ModifyMaterialForBasicMode(basic_mat);
670  scene_wgt_->GetScene()->UpdateMaterial(basic_mat);
671  } else {
672  scene_wgt_->GetScene()->UpdateMaterial(
674  }
675  break;
676  case GuiSettingsModel::MaterialType::NORMAL_MAP: {
678  scene_wgt_->GetScene()->UpdateMaterial(
680  } break;
681  case GuiSettingsModel::MaterialType::DEPTH: {
683  scene_wgt_->GetScene()->UpdateMaterial(
685  } break;
686 
687  default:
688  break;
689  }
690  }
691 
692  void UpdateMaterials(rendering::Renderer &renderer,
693  const GuiSettingsModel::Materials &materials) {
694  auto &lit = settings_.lit_material_;
695  auto &unlit = settings_.unlit_material_;
696  auto &normal_depth = settings_.normal_depth_material_;
697 
698  // Update lit from GUI
699  lit.base_color.x() = materials.lit.base_color.x();
700  lit.base_color.y() = materials.lit.base_color.y();
701  lit.base_color.z() = materials.lit.base_color.z();
702  lit.point_size = materials.point_size;
703  lit.base_metallic = materials.lit.metallic;
704  lit.base_roughness = materials.lit.roughness;
705  lit.base_reflectance = materials.lit.reflectance;
706  lit.base_clearcoat = materials.lit.clear_coat;
707  lit.base_clearcoat_roughness = materials.lit.clear_coat_roughness;
708  lit.base_anisotropy = materials.lit.anisotropy;
709 
710  // Update unlit from GUI
711  unlit.base_color.x() = materials.unlit.base_color.x();
712  unlit.base_color.y() = materials.unlit.base_color.y();
713  unlit.base_color.z() = materials.unlit.base_color.z();
714  unlit.point_size = materials.point_size;
715 
716  // Update normal/depth from GUI
717  normal_depth.point_size = materials.point_size;
718  }
719 
720  void OnNewIBL(Window &window, const char *name) {
722  path += std::string("/") + name + "_ibl.ktx";
723  if (!SetIBL(window.GetRenderer(), path)) {
724  // must be the "Custom..." option
725  auto dlg = std::make_shared<gui::FileDialog>(
726  gui::FileDialog::Mode::OPEN, "Open HDR Map",
727  window.GetTheme());
728  dlg->AddFilter(".ktx", "Khronos Texture (.ktx)");
729  dlg->SetOnCancel([&window]() { window.CloseDialog(); });
730  dlg->SetOnDone([this, &window](const char *path) {
731  window.CloseDialog();
732  SetIBL(window.GetRenderer(), path);
733  // We need to set the "custom" bit, so just call the current
734  // profile a custom profile.
737  });
738  window.ShowDialog(dlg);
739  }
740  }
741 };
742 
743 GuiVisualizer::GuiVisualizer(const std::string &title, int width, int height)
744  : gui::Window(title, width, height), impl_(new GuiVisualizer::Impl()) {
745  Init();
746 }
747 
749  const std::vector<std::shared_ptr<const geometry::Geometry>>
750  &geometries,
751  const std::string &title,
752  int width,
753  int height,
754  int left,
755  int top)
756  : gui::Window(title, left, top, width, height),
757  impl_(new GuiVisualizer::Impl()) {
758  Init();
759  SetGeometry(geometries[0], false); // also updates the camera
760 
761  // Create a message processor for incoming messages.
762  auto on_geometry = [this](std::shared_ptr<geometry::Geometry> geom,
763  const std::string &path, int time,
764  const std::string &layer) {
765  // Rather than duplicating the logic to figure out the correct material,
766  // just add with the default material and pretend the user changed the
767  // current material and update everyone's material.
768  impl_->scene_wgt_->GetScene()->AddGeometry(path, geom.get(),
770  impl_->UpdateFromModel(GetRenderer(), true);
771  };
772  impl_->message_processor_ =
773  std::make_shared<MessageProcessor>(this, on_geometry);
774 }
775 
776 void GuiVisualizer::Init() {
777  auto &app = gui::Application::GetInstance();
778  auto &theme = GetTheme();
779 
780  // Create menu
781  if (!gui::Application::GetInstance().GetMenubar()) {
782  auto menu = std::make_shared<gui::Menu>();
783 #if defined(__APPLE__)
784  // The first menu item to be added on macOS becomes the application
785  // menu (no matter its name)
786  auto app_menu = std::make_shared<gui::Menu>();
787  app_menu->AddItem("About", HELP_ABOUT);
788  app_menu->AddSeparator();
789  impl_->app_menu_custom_items_index_ = app_menu->GetNumberOfItems();
790  app_menu->AddItem("Quit", FILE_QUIT, gui::KEY_Q);
791  menu->AddMenu("CloudViewer", app_menu);
792  impl_->app_menu_ = app_menu;
793 #endif // __APPLE__
794  auto file_menu = std::make_shared<gui::Menu>();
795  file_menu->AddItem("Open...", FILE_OPEN, gui::KEY_O);
796  file_menu->AddItem("Export Current Image...", FILE_EXPORT_RGB);
797  file_menu->AddSeparator();
798 #if defined(_WIN32)
799  file_menu->AddItem("Exit", FILE_QUIT);
800 #elif !defined(__APPLE__) // quit goes in app menu on macOS
801  file_menu->AddItem("Quit", FILE_QUIT, gui::KEY_Q);
802 #endif
803  menu->AddMenu("File", file_menu);
804 
805  auto settings_menu = std::make_shared<gui::Menu>();
806  settings_menu->AddItem("Lighting & Materials",
808  settings_menu->SetChecked(SETTINGS_LIGHT_AND_MATERIALS, true);
809  menu->AddMenu("Settings", settings_menu);
810 
811  auto help_menu = std::make_shared<gui::Menu>();
812  help_menu->AddItem("Show Controls", HELP_KEYS);
813  help_menu->AddItem("Show Camera Info", HELP_CAMERA);
814  help_menu->AddSeparator();
815  help_menu->AddItem("About", HELP_ABOUT);
816  help_menu->AddItem("Contact", HELP_CONTACT);
817 #if defined(__APPLE__)
818  // macOS adds a special search item to menus named "Help",
819  // so add a space to avoid that.
820  menu->AddMenu("Help ", help_menu);
821 #else
822  menu->AddMenu("Help", help_menu);
823 #endif
824 
826  }
827 
828  // Implementation needs the GuiVisualizer
829  impl_->visualizer_ = this;
830 
831  // Create scene
832  impl_->scene_wgt_ = std::make_shared<gui::SceneWidget>();
833  impl_->scene_wgt_->SetScene(
834  std::make_shared<rendering::CloudViewerScene>(GetRenderer()));
835  impl_->scene_wgt_->SetOnSunDirectionChanged(
836  [this](const Eigen::Vector3f &new_dir) {
837  auto lighting = impl_->settings_.model_.GetLighting(); // copy
838  lighting.sun_dir = new_dir.normalized();
839  impl_->settings_.model_.SetCustomLighting(lighting);
840  });
841  impl_->scene_wgt_->EnableSceneCaching(true);
842 
843  // Create light
844  auto &settings = impl_->settings_;
845  std::string resource_path = app.GetResourcePath();
846  auto ibl_path = resource_path + "/default";
847  auto *render_scene = impl_->scene_wgt_->GetScene()->GetScene();
848  render_scene->SetIndirectLight(ibl_path);
849 
850  // Create materials
851  impl_->InitializeMaterials(GetRenderer(), resource_path);
852 
853  // Setup UI
854  const auto em = theme.font_size;
855  const int lm = int(std::ceil(0.5 * em));
856  const int grid_spacing = int(std::ceil(0.25 * em));
857 
858  AddChild(impl_->scene_wgt_);
859 
860  // Add settings widget
861  const int separation_height = int(std::ceil(0.75 * em));
862  // (we don't want as much left margin because the twisty arrow is the
863  // only thing there, and visually it looks larger than the right.)
864  const gui::Margins base_margins(int(std::round(0.5 * lm)), lm, lm, lm);
865  settings.wgt_base = std::make_shared<gui::Vert>(0, base_margins);
866 
867  gui::Margins indent(em, 0, 0, 0);
868  auto view_ctrls =
869  std::make_shared<gui::CollapsableVert>("Mouse controls", 0, indent);
870 
871  // ... view manipulator buttons
872  settings.wgt_mouse_arcball = std::make_shared<SmallToggleButton>("Arcball");
873  impl_->settings_.wgt_mouse_arcball->SetOn(true);
874  settings.wgt_mouse_arcball->SetOnClicked([this]() {
875  impl_->SetMouseControls(*this,
876  gui::SceneWidget::Controls::ROTATE_CAMERA);
877  });
878  settings.wgt_mouse_fly = std::make_shared<SmallToggleButton>("Fly");
879  settings.wgt_mouse_fly->SetOnClicked([this]() {
880  impl_->SetMouseControls(*this, gui::SceneWidget::Controls::FLY);
881  });
882  settings.wgt_mouse_model = std::make_shared<SmallToggleButton>("Model");
883  settings.wgt_mouse_model->SetOnClicked([this]() {
884  impl_->SetMouseControls(*this,
885  gui::SceneWidget::Controls::ROTATE_MODEL);
886  });
887  settings.wgt_mouse_sun = std::make_shared<SmallToggleButton>("Sun");
888  settings.wgt_mouse_sun->SetOnClicked([this]() {
889  impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_SUN);
890  });
891  settings.wgt_mouse_ibl = std::make_shared<SmallToggleButton>("Environment");
892  settings.wgt_mouse_ibl->SetOnClicked([this]() {
893  impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_IBL);
894  });
895 
896  auto reset_camera = std::make_shared<SmallButton>("Reset camera");
897  reset_camera->SetOnClicked([this]() {
898  impl_->scene_wgt_->GoToCameraPreset(
900  });
901 
902  auto camera_controls1 = std::make_shared<gui::Horiz>(grid_spacing);
903  camera_controls1->AddStretch();
904  camera_controls1->AddChild(settings.wgt_mouse_arcball);
905  camera_controls1->AddChild(settings.wgt_mouse_fly);
906  camera_controls1->AddChild(settings.wgt_mouse_model);
907  camera_controls1->AddStretch();
908  auto camera_controls2 = std::make_shared<gui::Horiz>(grid_spacing);
909  camera_controls2->AddStretch();
910  camera_controls2->AddChild(settings.wgt_mouse_sun);
911  camera_controls2->AddChild(settings.wgt_mouse_ibl);
912  camera_controls2->AddStretch();
913  view_ctrls->AddChild(camera_controls1);
914  view_ctrls->AddFixed(int(std::ceil(0.25 * em)));
915  view_ctrls->AddChild(camera_controls2);
916  view_ctrls->AddFixed(separation_height);
917  view_ctrls->AddChild(gui::Horiz::MakeCentered(reset_camera));
918  settings.wgt_base->AddChild(view_ctrls);
919 
920  // ... lighting and materials
921  settings.view_ = std::make_shared<GuiSettingsView>(
922  settings.model_, theme, resource_path, [this](const char *name) {
923  if (std::string(name) ==
924  std::string(GuiSettingsModel::CUSTOM_IBL)) {
925  auto dlg = std::make_shared<gui::FileDialog>(
926  gui::FileDialog::Mode::OPEN, "Open HDR Map",
927  GetTheme());
928  dlg->AddFilter(".ktx", "Khronos Texture (.ktx)");
929  dlg->SetOnCancel([this]() { CloseDialog(); });
930  dlg->SetOnDone([this](const char *path) {
931  CloseDialog();
932  impl_->SetIBL(GetRenderer(), path);
933  });
934  ShowDialog(dlg);
935  } else {
936  std::string resource_path =
937  gui::Application::GetInstance().GetResourcePath();
938  impl_->SetIBL(GetRenderer(),
939  resource_path + "/" + name + "_ibl.ktx");
940  }
941  });
942  settings.model_.SetOnChanged([this](bool material_type_changed) {
943  impl_->settings_.view_->Update();
944  impl_->UpdateFromModel(GetRenderer(), material_type_changed);
945  });
946  settings.wgt_base->AddChild(settings.view_);
947 
948  AddChild(settings.wgt_base);
949 
950  // Apply model settings (which should be defaults) to the rendering entities
951  impl_->UpdateFromModel(GetRenderer(), false);
952 
953  // Other items
954  impl_->help_keys_ = CreateHelpDisplay(this);
955  impl_->help_keys_->SetVisible(false);
956  AddChild(impl_->help_keys_);
957  impl_->help_camera_ = CreateCameraDisplay(this);
958  impl_->help_camera_->SetVisible(false);
959  AddChild(impl_->help_camera_);
960 }
961 
963 
964 void GuiVisualizer::SetTitle(const std::string &title) {
965  Super::SetTitle(title.c_str());
966 }
967 
969  const std::vector<std::pair<std::string, gui::Menu::ItemId>> &items) {
970 #if !defined(__APPLE__)
971  return; // application menu only exists on macOS
972 #endif
973 
974  if (impl_->app_menu_ && impl_->app_menu_custom_items_index_ >= 0) {
975  for (auto &it : items) {
976  impl_->app_menu_->InsertItem(impl_->app_menu_custom_items_index_++,
977  it.first.c_str(), it.second);
978  }
979  impl_->app_menu_->InsertSeparator(
980  impl_->app_menu_custom_items_index_++);
981  }
982 }
983 
985  std::shared_ptr<const geometry::Geometry> geometry, bool loaded_model) {
986  auto scene3d = impl_->scene_wgt_->GetScene();
987  scene3d->ClearGeometry();
988 
989  impl_->SetMaterialsToDefault();
990 
991  rendering::MaterialRecord loaded_material;
992  if (loaded_model) {
993  scene3d->AddModel(MODEL_NAME, impl_->loaded_model_);
994  impl_->settings_.model_.SetDisplayingPointClouds(false);
995  loaded_material.shader = "defaultLit";
996  } else {
997  // NOTE: If a model was NOT loaded then these must be point clouds
998  std::shared_ptr<const geometry::Geometry> g = geometry;
999 
1000  // If a point cloud or mesh has no vertex colors or a single uniform
1001  // color (usually white), then we want to display it normally, that
1002  // is, lit. But if the cloud/mesh has differing vertex colors, then
1003  // we assume that the vertex colors have the lighting value baked in
1004  // (for example, fountain.ply at http://qianyi.info/scenedata.html)
1005  if (g->isKindOf(CV_TYPES::POINT_CLOUD)) {
1006  auto pcd = std::static_pointer_cast<const geometry::PointCloud>(g);
1007 
1008  if (pcd->hasNormals()) {
1009  loaded_material.shader = "defaultLit";
1010  } else if (pcd->hasColors() && !PointCloudHasUniformColor(*pcd)) {
1011  loaded_material.shader = "defaultUnlit";
1012  } else {
1013  loaded_material.shader = "defaultLit";
1014  }
1015 
1016  scene3d->AddGeometry(MODEL_NAME, pcd.get(), loaded_material);
1017 
1018  impl_->settings_.model_.SetDisplayingPointClouds(true);
1019  impl_->settings_.view_->EnableEstimateNormals(true);
1020  if (!impl_->settings_.model_.GetUserHasChangedLightingProfile()) {
1021  auto &profile =
1023  impl_->settings_.model_.SetLightingProfile(profile);
1024  }
1025  }
1026  }
1027 
1028  auto type = impl_->settings_.model_.GetMaterialType();
1029  if (type == GuiSettingsModel::MaterialType::LIT ||
1030  type == GuiSettingsModel::MaterialType::UNLIT) {
1031  if (loaded_material.shader == "defaultUnlit") {
1032  impl_->settings_.model_.SetMaterialType(
1033  GuiSettingsModel::MaterialType::UNLIT);
1034  } else {
1035  impl_->settings_.model_.SetMaterialType(
1036  GuiSettingsModel::MaterialType::LIT);
1037  }
1038  }
1039 
1040  // Setup UI for loaded model/point cloud
1041  impl_->settings_.model_.UnsetCustomDefaultColor();
1042  if (loaded_model) {
1043  impl_->settings_.view_->ShowFileMaterialEntry(true);
1044  impl_->settings_.model_.SetCurrentMaterials(
1046  } else {
1047  impl_->settings_.view_->ShowFileMaterialEntry(false);
1048  }
1049  impl_->settings_.view_->Update(); // make sure prefab material is correct
1050 
1051  auto &bounds = scene3d->GetBoundingBox();
1052  impl_->scene_wgt_->SetupCamera(60.0, bounds,
1053  bounds.GetCenter().cast<float>());
1054 
1055  // Setup for raw mode if enabled...
1056  if (impl_->basic_mode_enabled_) {
1057  impl_->SetBasicModeGeometry(true);
1058  scene3d->GetScene()->SetSunLightDirection(
1059  scene3d->GetCamera()->GetForwardVector());
1060  }
1061 
1062  // Make sure scene is redrawn
1063  impl_->scene_wgt_->ForceRedraw();
1064 }
1065 
1067  auto r = GetContentRect();
1068  const auto em = context.theme.font_size;
1069  impl_->scene_wgt_->SetFrame(r);
1070 
1071  // Draw help keys HUD in upper left
1072  const auto pref = impl_->help_keys_->CalcPreferredSize(
1074  impl_->help_keys_->SetFrame(gui::Rect(0, r.y, pref.width, pref.height));
1075  impl_->help_keys_->Layout(context);
1076 
1077  // Draw camera HUD in lower left
1078  const auto prefcam = impl_->help_camera_->CalcPreferredSize(
1080  impl_->help_camera_->SetFrame(gui::Rect(0, r.height + r.y - prefcam.height,
1081  prefcam.width, prefcam.height));
1082  impl_->help_camera_->Layout(context);
1083 
1084  // Settings in upper right
1085  const auto LIGHT_SETTINGS_WIDTH = 18 * em;
1086  auto light_settings_size = impl_->settings_.wgt_base->CalcPreferredSize(
1088  gui::Rect lightSettingsRect(r.width - LIGHT_SETTINGS_WIDTH, r.y,
1089  LIGHT_SETTINGS_WIDTH,
1090  std::min(r.height, light_settings_size.height));
1091  impl_->settings_.wgt_base->SetFrame(lightSettingsRect);
1092 
1094 }
1095 
1096 void GuiVisualizer::StartRPCInterface(const std::string &address, int timeout) {
1097  impl_->receiver_ = std::make_shared<io::rpc::ZMQReceiver>(address, timeout);
1098  impl_->receiver_->SetMessageProcessor(impl_->message_processor_);
1099  try {
1100  utility::LogInfo("Starting to listen on {}", address);
1101  impl_->receiver_->Start();
1102  } catch (std::exception &e) {
1103  utility::LogWarning("Failed to start RPC interface: {}", e.what());
1104  }
1105 }
1106 
1107 void GuiVisualizer::StopRPCInterface() { impl_->receiver_.reset(); }
1108 
1109 bool GuiVisualizer::SetIBL(const char *path) {
1110  auto result = impl_->SetIBL(GetRenderer(), path);
1111  PostRedraw();
1112  return result;
1113 }
1114 
1115 void GuiVisualizer::LoadGeometry(const std::string &path) {
1116  auto progressbar = std::make_shared<gui::ProgressBar>();
1118  progressbar]() {
1119  auto &theme = GetTheme();
1120  auto loading_dlg = std::make_shared<gui::Dialog>("Loading");
1121  auto vert =
1122  std::make_shared<gui::Vert>(0, gui::Margins(theme.font_size));
1123  auto loading_text = std::string("Loading ") + path;
1124  vert->AddChild(std::make_shared<gui::Label>(loading_text.c_str()));
1125  vert->AddFixed(theme.font_size);
1126  vert->AddChild(progressbar);
1127  loading_dlg->AddChild(vert);
1128  ShowDialog(loading_dlg);
1129  });
1130 
1131  gui::Application::GetInstance().RunInThread([this, path, progressbar]() {
1132  auto UpdateProgress = [this, progressbar](float value) {
1134  this,
1135  [progressbar, value]() { progressbar->SetValue(value); });
1136  };
1137 
1138  // clear current model
1139  impl_->loaded_model_.meshes_.clear();
1140  impl_->loaded_model_.materials_.clear();
1141  impl_->basic_model_.meshes_.clear();
1142  impl_->basic_model_.materials_.clear();
1143  impl_->wireframe_model_.reset();
1144  impl_->loaded_pcd_.reset();
1145 
1146  auto geometry_type = io::ReadFileGeometryType(path);
1147 
1148  bool model_success = false;
1149  if (geometry_type & io::CONTAINS_TRIANGLES) {
1150  const float ioProgressAmount = 1.0f;
1151  try {
1153  opt.update_progress = [ioProgressAmount,
1154  UpdateProgress](double percent) -> bool {
1155  UpdateProgress(ioProgressAmount * float(percent / 100.0));
1156  return true;
1157  };
1158  model_success =
1159  io::ReadTriangleModel(path, impl_->loaded_model_, opt);
1160  } catch (...) {
1161  model_success = false;
1162  }
1163  }
1164  // path appears to be a point cloud...
1165  auto geometry = std::shared_ptr<geometry::Geometry>();
1166  if (!model_success) {
1167  auto cloud = std::make_shared<geometry::PointCloud>();
1168  bool success = false;
1169  const float ioProgressAmount = 0.5f;
1170  try {
1172  opt.update_progress = [ioProgressAmount,
1173  UpdateProgress](double percent) -> bool {
1174  UpdateProgress(ioProgressAmount * float(percent / 100.0));
1175  return true;
1176  };
1177  success = io::ReadPointCloud(path, *cloud, opt);
1178  } catch (...) {
1179  success = false;
1180  }
1181  if (success) {
1182  utility::LogInfo("Successfully read {}", path.c_str());
1183  UpdateProgress(ioProgressAmount);
1184  if (!cloud->hasNormals() && !cloud->hasColors()) {
1185  cloud->EstimateNormals();
1186  }
1187  UpdateProgress(0.666f);
1188  cloud->NormalizeNormals();
1189  UpdateProgress(0.75f);
1190  geometry = cloud;
1191  impl_->loaded_pcd_ = cloud;
1192  } else {
1193  utility::LogWarning("Failed to read points {}", path.c_str());
1194  cloud.reset();
1195  }
1196  }
1197 
1198  if (model_success || geometry) {
1200  this, [this, model_success, geometry]() {
1201  SetGeometry(geometry, model_success);
1202  CloseDialog();
1203  });
1204  } else {
1206  path]() {
1207  CloseDialog();
1208  auto msg = std::string("Could not load '") + path + "'.";
1209  ShowMessageBox("Error", msg.c_str());
1210  });
1211  }
1212  });
1213 }
1214 
1215 void GuiVisualizer::ExportCurrentImage(const std::string &path) {
1216  impl_->scene_wgt_->EnableSceneCaching(false);
1217  impl_->scene_wgt_->GetScene()->GetScene()->RenderToImage(
1218  [this, path](std::shared_ptr<geometry::Image> image) mutable {
1219  if (!io::WriteImage(path, *image)) {
1220  this->ShowMessageBox(
1221  "Error", (std::string("Could not write image to ") +
1222  path + ".")
1223  .c_str());
1224  }
1225  impl_->scene_wgt_->EnableSceneCaching(true);
1226  });
1227 }
1228 
1230  auto menu_id = MenuId(item_id);
1231  switch (menu_id) {
1232  case FILE_OPEN: {
1233  auto dlg = std::make_shared<gui::FileDialog>(
1234  gui::FileDialog::Mode::OPEN, "Open Geometry", GetTheme());
1235  dlg->AddFilter(".ply .stl .fbx .obj .off .gltf .glb",
1236  "Triangle mesh files (.ply, .stl, .fbx, .obj, .off, "
1237  ".gltf, .glb)");
1238  dlg->AddFilter(".xyz .xyzn .xyzrgb .ply .pcd .pts",
1239  "Point cloud files (.xyz, .xyzn, .xyzrgb, .ply, "
1240  ".pcd, .pts)");
1241  dlg->AddFilter(".ply", "Polygon files (.ply)");
1242  dlg->AddFilter(".stl", "Stereolithography files (.stl)");
1243  dlg->AddFilter(".fbx", "Autodesk Filmbox files (.fbx)");
1244  dlg->AddFilter(".obj", "Wavefront OBJ files (.obj)");
1245  dlg->AddFilter(".off", "Object file format (.off)");
1246  dlg->AddFilter(".gltf", "OpenGL transfer files (.gltf)");
1247  dlg->AddFilter(".glb", "OpenGL binary transfer files (.glb)");
1248  dlg->AddFilter(".xyz", "ASCII point cloud files (.xyz)");
1249  dlg->AddFilter(".xyzn", "ASCII point cloud with normals (.xyzn)");
1250  dlg->AddFilter(".xyzrgb",
1251  "ASCII point cloud files with colors (.xyzrgb)");
1252  dlg->AddFilter(".pcd", "Point Cloud Data files (.pcd)");
1253  dlg->AddFilter(".pts", "3D Points files (.pts)");
1254  dlg->AddFilter("", "All files");
1255  dlg->SetOnCancel([this]() { this->CloseDialog(); });
1256  dlg->SetOnDone([this](const char *path) {
1257  this->CloseDialog();
1258  OnDragDropped(path);
1259  });
1260  ShowDialog(dlg);
1261  break;
1262  }
1263  case FILE_EXPORT_RGB: {
1264  auto dlg = std::make_shared<gui::FileDialog>(
1265  gui::FileDialog::Mode::SAVE, "Save File", GetTheme());
1266  dlg->AddFilter(".png", "PNG images (.png)");
1267  dlg->AddFilter("", "All files");
1268  dlg->SetOnCancel([this]() { this->CloseDialog(); });
1269  dlg->SetOnDone([this](const char *path) {
1270  this->CloseDialog();
1271  this->ExportCurrentImage(path);
1272  });
1273  ShowDialog(dlg);
1274  break;
1275  }
1276  case FILE_QUIT:
1278  break;
1280  auto visibility = !impl_->settings_.wgt_base->IsVisible();
1281  impl_->settings_.wgt_base->SetVisible(visibility);
1282  auto menubar = gui::Application::GetInstance().GetMenubar();
1283  menubar->SetChecked(SETTINGS_LIGHT_AND_MATERIALS, visibility);
1284 
1285  // We need relayout because materials settings pos depends on light
1286  // settings visibility
1287  this->SetNeedsLayout();
1288 
1289  break;
1290  }
1291  case HELP_KEYS: {
1292  bool is_visible = !impl_->help_keys_->IsVisible();
1293  impl_->help_keys_->SetVisible(is_visible);
1294  auto menubar = gui::Application::GetInstance().GetMenubar();
1295  menubar->SetChecked(HELP_KEYS, is_visible);
1296  break;
1297  }
1298  case HELP_CAMERA: {
1299  bool is_visible = !impl_->help_camera_->IsVisible();
1300  impl_->help_camera_->SetVisible(is_visible);
1301  auto menubar = gui::Application::GetInstance().GetMenubar();
1302  menubar->SetChecked(HELP_CAMERA, is_visible);
1303  if (is_visible) {
1304  impl_->scene_wgt_->SetOnCameraChanged([this](rendering::Camera
1305  *cam) {
1306  auto children = this->impl_->help_camera_->GetChildren();
1307  auto set_text = [](const Eigen::Vector3f &v,
1308  std::shared_ptr<gui::Widget> label) {
1309  auto l = std::dynamic_pointer_cast<gui::Label>(label);
1310  l->SetText(fmt::format("[{:.2f} {:.2f} "
1311  "{:.2f}]",
1312  v.x(), v.y(), v.z())
1313  .c_str());
1314  };
1315  set_text(cam->GetPosition(), children[1]);
1316  set_text(cam->GetForwardVector(), children[3]);
1317  set_text(cam->GetLeftVector(), children[5]);
1318  set_text(cam->GetUpVector(), children[7]);
1319  this->SetNeedsLayout();
1320  });
1321  } else {
1322  impl_->scene_wgt_->SetOnCameraChanged(
1323  std::function<void(rendering::Camera *)>());
1324  }
1325  break;
1326  }
1327  case HELP_ABOUT: {
1328  auto dlg = CreateAboutDialog(this);
1329  ShowDialog(dlg);
1330  break;
1331  }
1332  case HELP_CONTACT: {
1333  auto dlg = CreateContactDialog(this);
1334  ShowDialog(dlg);
1335  break;
1336  }
1337  case HELP_DEBUG: {
1338  break;
1339  }
1340  }
1341 }
1342 
1344  auto title = std::string("CloudViewer - ") + path;
1345 #if LOAD_IN_NEW_WINDOW
1346  auto frame = this->GetFrame();
1347  std::vector<std::shared_ptr<const geometry::Geometry>> nothing;
1348  auto vis = std::make_shared<GuiVisualizer>(nothing, title.c_str(),
1349  frame.width, frame.height,
1350  frame.x + 20, frame.y + 20);
1352 #else
1353  this->SetTitle(title);
1354  auto vis = this;
1355 #endif // LOAD_IN_NEW_WINDOW
1356  vis->LoadGeometry(path);
1357 }
1358 
1359 } // namespace visualization
1360 } // namespace cloudViewer
Rect frame
std::shared_ptr< core::Tensor > image
filament::Texture::InternalFormat format
int width
std::string name
int height
char type
math::float4 color
CloudViewerScene::LightingProfile profile
core::Tensor result
Definition: VtkUtils.cpp:76
static std::shared_ptr< LineSet > CreateFromTriangleMesh(const ccMesh &mesh)
const Eigen::Vector3f & GetBackgroundColor() const
static constexpr const char * DEFAULT_IBL
static constexpr const char * MATERIAL_FROM_FILE_NAME
void SetCustomLighting(const LightingProfile &profile)
static const LightingProfile & GetDefaultPointCloudLightingProfile()
const LightingProfile & GetLighting() const
void ExportCurrentImage(const std::string &path)
void StartRPCInterface(const std::string &address, int timeout)
Starts the RPC interface. See io/rpc/ReceiverBase for the parameters.
void OnMenuItemSelected(gui::Menu::ItemId item_id) override
void Layout(const gui::LayoutContext &context) override
void LoadGeometry(const std::string &path)
Loads asynchronously, will return immediately.
void SetTitle(const std::string &title)
void AddItemsToAppMenu(const std::vector< std::pair< std::string, gui::Menu::ItemId >> &items)
void OnDragDropped(const char *path) override
void SetGeometry(std::shared_ptr< const ccHObject > geometry, bool loaded_model)
GuiVisualizer(const std::string &title, int width, int height)
void SetMenubar(std::shared_ptr< Menu > menubar)
std::shared_ptr< Menu > GetMenubar() const
void RunInThread(std::function< void()> f)
void AddWindow(std::shared_ptr< Window > window)
Must be called on the same thread that calls Run()
void Quit()
Closes all the windows, which exits as a result.
void PostToMainThread(Window *window, std::function< void()> f)
static std::shared_ptr< Horiz > MakeCentered(std::shared_ptr< Widget > w)
Definition: Layout.cpp:531
void SetFocusWidget(Widget *w)
Sets.
Definition: Window.cpp:595
void SetNeedsLayout()
Instructs the window to relayout before the next draw.
Definition: Window.cpp:571
const Theme & GetTheme() const
Definition: Window.cpp:445
void AddChild(std::shared_ptr< Widget > w)
Definition: Window.cpp:597
visualization::rendering::Renderer & GetRenderer() const
Definition: Window.cpp:447
void SetTitle(const char *title)
Definition: Window.cpp:466
virtual void Layout(const LayoutContext &context)
Definition: Window.cpp:681
void ShowDialog(std::shared_ptr< Dialog > dlg)
Definition: Window.cpp:619
void CloseDialog()
Closes the dialog.
Definition: Window.cpp:648
void ShowMessageBox(const char *title, const char *message)
Definition: Window.cpp:661
virtual Eigen::Vector3f GetUpVector() const =0
virtual Eigen::Vector3f GetLeftVector() const =0
virtual Eigen::Vector3f GetForwardVector() const =0
virtual Eigen::Vector3f GetPosition() const =0
double colors[3]
#define LogWarning(...)
Definition: Logging.h:72
#define LogInfo(...)
Definition: Logging.h:81
static void UpdateProgress(unsigned increment)
Definition: TrueKdTree.cpp:62
int min(int a, int b)
Definition: cutil_math.h:53
ImGuiContext * context
Definition: Window.cpp:76
const Theme * theme
Definition: Window.cpp:74
@ POINT_CLOUD
Definition: CVTypes.h:104
::ccPointCloud PointCloud
Definition: PointCloud.h:19
bool WriteImage(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
FileGeometry ReadFileGeometryType(const std::string &path)
bool ReadPointCloud(const std::string &filename, ccPointCloud &pointcloud, const ReadPointCloudOption &params)
bool ReadTriangleModel(const std::string &filename, visualization::rendering::TriangleMeshModel &model, ReadTriangleModelOptions params)
Definition: ModelIO.cpp:22
static const std::string path
Definition: PointCloud.cpp:59
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
const std::string WIREFRAME_NAME
void Draw(const std::vector< std::shared_ptr< ccHObject >> &geometries, const std::string &window_name, int width, int height, const std::vector< DrawAction > &actions)
Definition: Draw.cpp:45
const std::string INSPECT_MODEL_NAME
const std::string MODEL_NAME
Generic file read and write utility for python interface.
struct Window Window
Definition: sqlite3.c:14678
Optional parameters to ReadPointCloud.
Definition: FileIO.h:39
std::function< bool(double)> update_progress
Definition: FileIO.h:72
std::function< bool(double)> update_progress
Definition: ModelIO.h:30
std::shared_ptr< io::rpc::ZMQReceiver > receiver_
void SetMouseControls(gui::Window &window, gui::SceneWidget::Controls mode)
bool SetIBL(rendering::Renderer &renderer, const std::string &path)
void ModifyMaterialForBasicMode(rendering::MaterialRecord &basic_mat)
void UpdateFromModel(rendering::Renderer &renderer, bool material_changed)
std::shared_ptr< MessageProcessor > message_processor_
std::shared_ptr< geometry::PointCloud > loaded_pcd_
void InitializeMaterials(rendering::Renderer &renderer, const std::string &resource_path)
std::shared_ptr< geometry::LineSet > wireframe_model_
std::shared_ptr< gui::SceneWidget > scene_wgt_
struct cloudViewer::visualization::GuiVisualizer::Impl::Settings settings_
std::shared_ptr< geometry::Image > normal_img
std::shared_ptr< geometry::Image > albedo_img
std::shared_ptr< geometry::Image > metallic_img
std::shared_ptr< geometry::Image > roughness_img
std::shared_ptr< geometry::Image > reflectance_img
std::vector< visualization::rendering::MaterialRecord > materials_
Definition: Model.h:26