Program Listing for File MolaViz.h

Return to documentation for file (include/mola_viz/MolaViz.h)

/*               _
 _ __ ___   ___ | | __ _
| '_ ` _ \ / _ \| |/ _` | Modular Optimization framework for
| | | | | | (_) | | (_| | Localization and mApping (MOLA)
|_| |_| |_|\___/|_|\__,_| https://github.com/MOLAorg/mola

 Copyright (C) 2018-2026 Jose Luis Blanco, University of Almeria,
                         and individual contributors.
 SPDX-License-Identifier: GPL-3.0
 See LICENSE for full license information.
*/
#pragma once

#include <mola_kernel/interfaces/Dataset_UI.h>
#include <mola_kernel/interfaces/ExecutableBase.h>
#include <mola_kernel/interfaces/VizInterface.h>
#include <mrpt/gui/CDisplayWindowGUI.h>

#include <deque>
#include <future>
#include <memory>
#include <optional>
#include <set>
#include <shared_mutex>
#include <vector>

namespace mola
{
class MolaViz : public ExecutableBase, public VizInterface
{
  DEFINE_MRPT_OBJECT(MolaViz, mola)

 public:
  MolaViz();
  ~MolaViz() override;

  // Non-copyable, non-movable (owns a GUI thread and static singleton).
  MolaViz(const MolaViz&)            = delete;
  MolaViz& operator=(const MolaViz&) = delete;
  MolaViz(MolaViz&&)                 = delete;
  MolaViz& operator=(MolaViz&&)      = delete;

  // ExecutableBase overrides
  void initialize(const Yaml& cfg) override;
  void spinOnce() override;

  // =========================================================================

  using window_name_t    = std::string;
  using subwindow_name_t = std::string;

  static const window_name_t DEFAULT_WINDOW_NAME;

  static bool     IsRunning();
  static MolaViz* Instance();


  // =========================================================================

  [[nodiscard]] const std::string& gui_backend() const noexcept override;

  std::future<void> create_subwindow_from_description(
      const mola::gui::WindowDescription& desc,
      const std::string&                  parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<void> enqueue_custom_gui_code(const std::function<void()>& userCode) override;

  void* get_subwindow_handle(
      const std::string& subWindowTitle,
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<std::optional<std::string>> open_file_dialog(
      const std::string& title, bool save,
      const std::vector<std::pair<std::string, std::string>>& filters      = {},
      const std::string&                                      default_path = "",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<void> set_menu_bar(
      const mola::gui::MenuBar& bar,
      const std::string&        parentWindow = DEFAULT_WINDOW_NAME) override;

  MetricChannel::Ptr register_metric(
      const std::string& name, const std::string& unit = "") override;

  void push_metric(const std::string& name, double t, double value) override;


  // =========================================================================

  std::future<bool> update_3d_object(
      const std::string& objName, const std::shared_ptr<mrpt::opengl::CSetOfObjects>& obj,
      const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME,
      const std::string& parentFrame  = "") override;

  std::future<bool> update_3d_object_frame(
      const std::string& frameName, const mrpt::math::TPose3D& pose,
      const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<bool> insert_point_cloud_with_decay(
      const std::shared_ptr<mrpt::opengl::CPointCloudColoured>& cloud, double decay_time_seconds,
      const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME,
      const std::string& parentFrame  = "") override;

  std::future<bool> clear_all_point_clouds_with_decay(
      const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<bool> update_viewport_look_at(
      const mrpt::math::TPoint3Df& lookAt, const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME,
      const std::string& parentFrame  = "") override;

  std::future<bool> update_viewport_camera_azimuth(
      double azimuth, bool absolute_falseForRelative = true,
      const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<bool> update_viewport_camera_orthographic(
      bool orthographic, const std::string& viewportName = "main",
      const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;

  std::future<bool> execute_custom_code_on_background_scene(
      const std::function<void(mrpt::opengl::Scene&)>& userCode,
      const std::string&                               parentWindow = DEFAULT_WINDOW_NAME) override;


  // =========================================================================

  std::future<bool> subwindow_update_visualization(
      const mrpt::rtti::CObject::Ptr& obj, const std::string& subWindowTitle,
      const mrpt::containers::yaml* extra_parameters = nullptr,
      const std::string&            parentWindow     = DEFAULT_WINDOW_NAME) override;


  // =========================================================================

  std::future<bool> output_console_message(
      const std::string& message, const std::string& parentWindow = DEFAULT_WINDOW_NAME) override;


  // =========================================================================

  using update_handler_t = std::function<void(
      const mrpt::rtti::CObject::Ptr&, nanogui::Window* subWin, const window_name_t& parentWin,
      MolaViz* instance, const mrpt::containers::yaml* extra_parameters)>;
  using class_name_t     = std::string;

  static void register_gui_handler(const class_name_t& name, const update_handler_t& handler);


  // =========================================================================

  double       console_text_font_size_   = 9.0;
  unsigned int max_console_lines_        = 5;
  bool         show_rgbd_as_point_cloud_ = false;

  double assumed_sensor_rate_hz_ = 10.0;


  void markWindowForReLayout(const window_name_t& name)
  {
    auto lck = mrpt::lockHelper(guiThreadPendingTasksMtx_);
    guiThreadMustReLayoutTheseWindows_.insert(name);
  }

 private:
  // ---------------------------------------------------------------------------
  // Singleton
  // ---------------------------------------------------------------------------
  static MolaViz*          instance_;
  static std::shared_mutex instanceMtx_;

  // ---------------------------------------------------------------------------
  // Per-window state
  // ---------------------------------------------------------------------------

  struct DecayingCloud
  {
    DecayingCloud() = default;
    DecayingCloud(
        std::string                                               opengl_viewport_name_,
        const std::shared_ptr<mrpt::opengl::CPointCloudColoured>& cloud_, float initial_alpha_)
        : opengl_viewport_name(std::move(opengl_viewport_name_)),
          cloud(cloud_),
          initial_alpha(initial_alpha_)
    {
    }

    std::string                                        opengl_viewport_name;
    std::shared_ptr<mrpt::opengl::CPointCloudColoured> cloud;
    mrpt::opengl::CSetOfObjects::Ptr container;  // owning container at insert time
    float                            initial_alpha = 1.0f;
  };

  struct PerWindowData
  {
    mrpt::gui::CDisplayWindowGUI::Ptr win;
    std::vector<std::string>          console_messages;
    std::deque<DecayingCloud>         decaying_clouds;
    size_t                            max_decaying_clouds = 100;
  };

  std::map<window_name_t, PerWindowData>                                windows_;
  std::map<window_name_t, std::map<subwindow_name_t, nanogui::Window*>> subWindows_;

  mrpt::gui::CDisplayWindowGUI::Ptr create_and_add_window(const window_name_t& name);

  static std::shared_ptr<mrpt::opengl::CSetOfObjects> get_or_create_frame_node_(
      mrpt::opengl::Scene& scene, const std::string& frameName, const std::string& viewportName);

  mutable std::shared_mutex subWindowsMtx_;

  // ---------------------------------------------------------------------------
  // GUI thread and task queue
  // ---------------------------------------------------------------------------

  std::thread guiThread_;
  void        gui_thread();

  using task_queue_t = std::vector<std::function<void()>>;
  task_queue_t            guiThreadPendingTasks_;
  std::set<window_name_t> guiThreadMustReLayoutTheseWindows_;
  std::mutex              guiThreadPendingTasksMtx_;

  // ---------------------------------------------------------------------------
  // LiveString polling (called every GUI frame from gui_thread loop callback)
  // ---------------------------------------------------------------------------

  void poll_live_strings_in_subwindows_();

  std::map<uint64_t, mola::gui::LiveString::Ptr> liveStringRegistry_;

  // ---------------------------------------------------------------------------
  // Dataset_UI auto-generated panels
  // ---------------------------------------------------------------------------

  double lastTimeCheckForNewModules_ = 0;
  double lastTimeUpdateDatasetUIs_   = 0;

  struct DataPerDatasetUI
  {
    std::weak_ptr<mola::Dataset_UI> module;

    bool first_time_seen = true;

    mola::gui::LiveString::Ptr lbPlaybackPosition;

    // Note: slider, cbPaused, cmRate are now owned by the nanogui widget tree
    // created via create_subwindow_from_description().  They are not held here
    // because the description callbacks capture weak_ptr<Dataset_UI> directly,
    // making raw widget pointers unnecessary.
  };
  std::map<std::string, DataPerDatasetUI> datasetUIs_;

  void dataset_ui_check_new_modules();
  void dataset_ui_update();

  // ---------------------------------------------------------------------------
  // Decaying clouds
  // ---------------------------------------------------------------------------

  void internal_handle_decaying_clouds();
};

}  // namespace mola