/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "_DSceneViewerMain.h"

#include <mrpt/system/string_utils.h>  // firstNLines()
#include <wx/app.h>

#include "CDlgCamTracking.h"
#include "CDlgPLYOptions.h"

//(*InternalHeaders(_DSceneViewerFrame)
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/icon.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/string.h>
#include <wx/tglbtn.h>
//*)

#include <wx/busyinfo.h>
#include <wx/choicdlg.h>
#include <wx/colordlg.h>
#include <wx/dcmemory.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/imaglist.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/numdlg.h>
#include <wx/progdlg.h>
#include <wx/textdlg.h>

#include "CDialogOptions.h"

#if defined(__WXMSW__)
const std::string iniFileSect("CONF_WIN");
#elif defined(__UNIX__)
const std::string iniFileSect("CONF_LIN");
#endif

#include "../wx-common/Applications.xpm"
#include "../wx-common/ArrowLeft2.xpm"
#include "../wx-common/Folderdownloads.xpm"
#include "../wx-common/Qmark.xpm"
#include "../wx-common/empty_file.xpm"
#include "../wx-common/icon_backward.xpm"
#include "../wx-common/icon_cubes.xpm"
#include "../wx-common/icon_forward.xpm"
#include "../wx-common/icon_play.xpm"
#include "../wx-common/icon_record.xpm"
#include "../wx-common/icon_undo.xpm"
#include "../wx-common/mrpt_logo.xpm"
#include "imgs/icono_main.xpm"
//
#include "../wx-common/return_bitmap.h"

#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild wxWidgets"
#endif

#include <mrpt/config/CConfigFile.h>
#include <mrpt/gui/CWxGLCanvasBase.h>
#include <mrpt/gui/about_box.h>
#include <mrpt/io/CFileGZInputStream.h>
#include <mrpt/io/CFileGZOutputStream.h>
#include <mrpt/maps/CColouredPointsMap.h>
#include <mrpt/maps/CPointsMap.h>
#include <mrpt/opengl/CAngularObservationMesh.h>  // It's in lib mrpt-maps
#include <mrpt/opengl/CAssimpModel.h>
#include <mrpt/opengl/CFBORender.h>
#include <mrpt/opengl/CGridPlaneXY.h>
#include <mrpt/opengl/CPlanarLaserScan.h>  // It's in lib mrpt-maps
#include <mrpt/opengl/CPointCloud.h>
#include <mrpt/opengl/CPointCloudColoured.h>
#include <mrpt/opengl/Scene.h>
#include <mrpt/opengl/stock_objects.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/system/CDirectoryExplorer.h>
#include <mrpt/system/CTicTac.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/string_utils.h>
#if MRPT_HAS_LIBLAS
#include <mrpt/maps/CPointsMap_liblas.h>
#endif

const mrpt::maps::CColouredPointsMap dummy_map;  // this is to enforce to load
// the mrpt-maps DLL, then
// register all OpenGL classes
// defined there.

// A custom Art provider for customizing the icons:
class MyArtProvider : public wxArtProvider
{
 protected:
  wxBitmap CreateBitmap(const wxArtID& id, const wxArtClient& client, const wxSize& size) override;
};

// CreateBitmap function
wxBitmap MyArtProvider::CreateBitmap(
    const wxArtID& id, const wxArtClient& client, const wxSize& size)
{
  if (id == wxART_MAKE_ART_ID(MAIN_ICON)) return wxBitmap(icono_main_xpm);
  if (id == wxART_MAKE_ART_ID(IMG_MRPT_LOGO)) return wxBitmap(mrpt_logo_xpm);

  RETURN_BITMAP(wxART_NORMAL_FILE, empty_file_xpm)
  RETURN_BITMAP(wxART_FILE_OPEN, Folderdownloads_xpm);
  RETURN_BITMAP(wxART_REMOVABLE, icon_play_xpm);
  RETURN_BITMAP(wxART_HELP_BOOK, Qmark_xpm);
  RETURN_BITMAP(wxART_QUIT, ArrowLeft2_xpm);
  RETURN_BITMAP(wxART_GO_FORWARD, icon_forward_xpm);
  RETURN_BITMAP(wxART_GO_BACK, icon_backward_xpm);
  RETURN_BITMAP(wxART_FIND, Applications_xpm);
  RETURN_BITMAP(wxART_TICK_MARK, icon_cubes_xpm);
  RETURN_BITMAP(wxART_REDO, icon_undo_xpm);
  RETURN_BITMAP(wxART_HARDDISK, icon_record_xpm);

  // Any wxWidgets icons not implemented here
  // will be provided by the default art provider.
  return wxNullBitmap;
}

// Used for feedback from the glcanvas component to its parent.
_DSceneViewerFrame* theWindow = nullptr;

#include <mutex>

using namespace mrpt;
using namespace mrpt::system;
using namespace mrpt::poses;
using namespace mrpt::math;
using namespace mrpt::io;
using namespace mrpt::config;
using namespace mrpt::rtti;
using namespace mrpt::opengl;
using namespace mrpt::img;
using namespace std;

// Critical section for updating the scene:
std::mutex critSec_UpdateScene;

// The file to open (from cmd line), or an empty string
extern std::string global_fileToOpen;
// The configuration file:
extern std::unique_ptr<CConfigFile> iniFile;

bool isCapturing = false;
string capturingDir = ".";
int captureCount = 0;

string loadedFileName;

bool freeCameraAlways = false;
bool freeCameraAlwaysNoAzimuth = false;

bool showFileNameInViewport = false;
int delayBetweenAutoplay = 5;

wxLogWindow* logWin = nullptr;

void saveLastUsedDirectoryToCfgFile(const std::string& fil)
{
  WX_START_TRY

  iniFile->write(iniFileSect, "LastDir", extractFileDirectory(fil));

  WX_END_TRY
}

void CMyGLCanvas::OnRenderError(const wxString& str)
{
  const size_t maxLines = 7;
  const std::string sErr = mrpt::system::firstNLines(str.ToStdString(), maxLines);

  if (!logWin)
  {
    logWin = new wxLogWindow(this, wxT("Log window"), false);
    logWin->Show();
  }

  static double lastErrMsg = 0;
  double tNow = mrpt::Clock::nowDouble();
  if (tNow - lastErrMsg > 10.0)
  {
    lastErrMsg = tNow;
    wxLogError(wxString(sErr));
  }
}

void CMyGLCanvas::OnPreRender() {}

void CMyGLCanvas::OnPostRenderSwapBuffers(double At, wxPaintDC& dc)
{
  // Capture window image?
  // --------------------------
  if (isCapturing)
  {
    int w, h;
    dc.GetSize(&w, &h);

    // Save image directly from OpenGL
    CImage frame(w, h, CH_RGB);

    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);

    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, w, h, GL_BGR_EXT, GL_UNSIGNED_BYTE, frame(0, 0));
    frame.flipVertical();

    string fileName(format("%s/screenshot_%07i.png", capturingDir.c_str(), captureCount++));

    bool savedOk = frame.saveToFile(fileName);
    ASSERT_(savedOk);
  }

  // Estimate FPS:
  // --------------------------
  double estimatedFPS;
  static double meanEstimatedFPS = 1;

  if (At > 0)
    estimatedFPS = 1 / At;
  else
    estimatedFPS = 0;

  meanEstimatedFPS = 0.8 * meanEstimatedFPS + 0.2 * estimatedFPS;

  string str = format(
      "Center=(%.02f,%.02f,%.02f) Zoom:%.02f AZ=%.02f deg EL:%.02f deg", getCameraPointingX(),
      getCameraPointingY(), getCameraPointingZ(), getZoomDistance(), getAzimuthDegrees(),
      getElevationDegrees());
  theWindow->StatusBar1->SetStatusText(str.c_str(), 1);

  str = format("%.02f FPS", meanEstimatedFPS);
  theWindow->StatusBar1->SetStatusText(str.c_str(), 2);

  str = format("%u viewports", (unsigned)getOpenGLSceneRef()->viewportsCount());
  theWindow->StatusBar1->SetStatusText(str.c_str(), 3);
}

void CMyGLCanvas::OnPostRender() {}

void CMyGLCanvas::OnCharCustom(wxKeyEvent& event)
{
  long evkey = event.GetKeyCode();

  if (evkey == WXK_LEFT || evkey == WXK_RIGHT)
  {
    try
    {
      // Load a different file:

      // First, build a list of files in the directory:
      static CDirectoryExplorer::TFileInfoList lstFiles;
      static TTimeStamp lastUpdateOfList = INVALID_TIMESTAMP;
      TTimeStamp curTime = mrpt::Clock::now();

      if (lastUpdateOfList == INVALID_TIMESTAMP || timeDifference(lastUpdateOfList, curTime) > 2)
      {
        CDirectoryExplorer::explore(
            extractFileDirectory(mrpt::system::toAbsolutePath(loadedFileName)),  // path
            FILE_ATTRIB_ARCHIVE,                                                 // mask
            lstFiles);

        // Sort and remove non 3Dscene files:
        CDirectoryExplorer::sortByName(lstFiles);
        CDirectoryExplorer::filterByExtension(lstFiles, "3Dscene");

        lastUpdateOfList = curTime;
      }

      string curFileName =
          extractFileName(loadedFileName) + string(".") + extractFileExtension(loadedFileName);

      // Find the current file:
      CDirectoryExplorer::TFileInfoList::iterator it;
      size_t i;
      for (i = 0, it = lstFiles.begin(); it != lstFiles.end(); it++, i++)
      {
        if (!os::_strcmpi(it->name.c_str(), curFileName.c_str()))
        {
          // Look for the desired file:
          std::optional<std::string> fil;
          if (evkey == WXK_LEFT)
          {
            if (i > 0) fil = lstFiles[i - 1].wholePath;
          }
          else
          {
            if (i < (lstFiles.size() - 1)) fil = lstFiles[i + 1].wholePath;
          }

          if (fil.has_value()) theWindow->loadFromFile(fil.value(), true);
          return;
        }
      }
    }
    catch (const std::exception& e)
    {
      std::cerr << "*EXCEPTION* while determining next/previous file:\n" << e.what() << std::endl;
    }
  }
}

//(*IdInit(_DSceneViewerFrame)
const long _DSceneViewerFrame::ID_BUTTON1 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON2 = wxNewId();
const long _DSceneViewerFrame::ID_STATICLINE1 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON3 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON4 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON5 = wxNewId();
const long _DSceneViewerFrame::ID_STATICLINE2 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON6 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON7 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON8 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON9 = wxNewId();
const long _DSceneViewerFrame::ID_STATICLINE3 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON10 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON11 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM1 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM2 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM5 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM7 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM6 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM_ImportImage = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM20 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM25 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM19 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM22 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM21 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM29 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM30 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM12 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM23 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM18 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM_PRINT_TEXT = wxNewId();
const long _DSceneViewerFrame::idMenuQuit = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM24 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM26 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM27 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM28 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM4 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM3 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM15 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM17 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM16 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM11 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM9 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM8 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM10 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM14 = wxNewId();
const long _DSceneViewerFrame::ID_MENUITEM13 = wxNewId();
const long _DSceneViewerFrame::idMenuAbout = wxNewId();
const long _DSceneViewerFrame::ID_STATUSBAR1 = wxNewId();
const long _DSceneViewerFrame::ID_TIMER1 = wxNewId();
//*)

const long _DSceneViewerFrame::ID_TRAVELLING_TIMER = wxNewId();

const long _DSceneViewerFrame::ID_TIMER_AUTOPLAY = wxNewId();

BEGIN_EVENT_TABLE(_DSceneViewerFrame, wxFrame)
//(*EventTable(_DSceneViewerFrame)
//*)
END_EVENT_TABLE()

_DSceneViewerFrame::_DSceneViewerFrame(wxWindow* parent, wxWindowID id) : maxv(0)
{
  theWindow = this;

  // Load my custom icons:
  wxArtProvider::Push(new MyArtProvider);

  //(*Initialize(_DSceneViewerFrame)
  wxMenuItem* MenuItem2;
  wxMenu* MenuItem15;
  wxMenuItem* MenuItem1;
  wxMenuItem* MenuItem4;
  wxFlexGridSizer* FlexGridSizer2;
  wxMenuItem* MenuItem13;
  wxMenu* Menu1;
  wxMenuItem* MenuItem12;
  wxMenuItem* MenuItem3;
  wxMenuBar* MenuBar1;
  wxFlexGridSizer* FlexGridSizer1;
  wxMenu* Menu2;

  Create(
      parent, id, _("SceneViewer3D - Part of the MRPT project"), wxDefaultPosition, wxDefaultSize,
      wxDEFAULT_FRAME_STYLE, _T("id"));
  SetMinSize(wxSize(150, 100));
  {
    wxIcon FrameIcon;
    FrameIcon.CopyFromBitmap(
        wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("MAIN_ICON")), wxART_FRAME_ICON));
    SetIcon(FrameIcon);
  }
  FlexGridSizer1 = new wxFlexGridSizer(1, 2, 0, 0);
  FlexGridSizer1->AddGrowableCol(1);
  FlexGridSizer1->AddGrowableRow(0);
  FlexGridSizer2 = new wxFlexGridSizer(0, 1, 0, 0);
  btnNew = new wxCustomButton(
      this, ID_BUTTON1, _("  New  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_NORMAL_FILE")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON1"));
  btnNew->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_NORMAL_FILE")), wxART_TOOLBAR));
  btnNew->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnNew, 1, wxEXPAND, 1);
  btnToolbarOpen = new wxCustomButton(
      this, ID_BUTTON2, _("  Open... "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_OPEN")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON2"));
  btnToolbarOpen->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_OPEN")), wxART_TOOLBAR));
  btnToolbarOpen->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnToolbarOpen, 1, wxEXPAND, 1);
  StaticLine1 = new wxStaticLine(
      this, ID_STATICLINE1, wxDefaultPosition, wxSize(50, -1), wxLI_HORIZONTAL,
      _T("ID_STATICLINE1"));
  FlexGridSizer2->Add(
      StaticLine1, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 2);
  btnPrev = new wxCustomButton(
      this, ID_BUTTON3, _("  Previous  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_GO_BACK")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON3"));
  btnPrev->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_GO_BACK")), wxART_TOOLBAR));
  btnPrev->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnPrev, 1, wxEXPAND, 1);
  btnNext = new wxCustomButton(
      this, ID_BUTTON4, _("  Next  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_GO_FORWARD")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON4"));
  btnNext->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_GO_FORWARD")), wxART_TOOLBAR));
  btnNext->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnNext, 1, wxEXPAND, 1);
  btnReload = new wxCustomButton(
      this, ID_BUTTON5, _("  Reload  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REDO")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON5"));
  btnReload->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REDO")), wxART_TOOLBAR));
  btnReload->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnReload, 1, wxEXPAND, wxDLG_UNIT(this, wxSize(1, 0)).GetWidth());
  StaticLine2 = new wxStaticLine(
      this, ID_STATICLINE2, wxDefaultPosition, wxSize(50, -1), wxLI_HORIZONTAL,
      _T("ID_STATICLINE2"));
  FlexGridSizer2->Add(
      StaticLine2, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 2);
  btnOptions = new wxCustomButton(
      this, ID_BUTTON6, _("  Options  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FIND")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON6"));
  btnOptions->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FIND")), wxART_TOOLBAR));
  btnOptions->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnOptions, 1, wxEXPAND, 1);
  btnOrtho = new wxCustomButton(
      this, ID_BUTTON7, _("  Ortho  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_TOGGLE | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON7"));
  btnOrtho->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR));
  btnOrtho->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnOrtho, 1, wxEXPAND, 1);
  btnAutoplay = new wxCustomButton(
      this, ID_BUTTON8, _("  Autoplay  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REMOVABLE")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_TOGGLE | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON8"));
  btnAutoplay->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REMOVABLE")), wxART_TOOLBAR));
  btnAutoplay->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnAutoplay, 1, wxEXPAND, 1);
  btnCapture = new wxCustomButton(
      this, ID_BUTTON9, _("  Capture  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_HARDDISK")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_TOGGLE | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON9"));
  btnCapture->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_HARDDISK")), wxART_TOOLBAR));
  btnCapture->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnCapture, 1, wxEXPAND, 1);
  StaticLine3 = new wxStaticLine(
      this, ID_STATICLINE3, wxDefaultPosition, wxSize(50, -1), wxLI_HORIZONTAL,
      _T("ID_STATICLINE3"));
  FlexGridSizer2->Add(
      StaticLine3, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 2);
  btnAbout = new wxCustomButton(
      this, ID_BUTTON10, _("  About..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_HELP_BOOK")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON10"));
  btnAbout->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_HELP_BOOK")), wxART_TOOLBAR));
  btnAbout->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnAbout, 1, wxEXPAND, 1);
  btnQuit = new wxCustomButton(
      this, ID_BUTTON11, _("  Quit  "),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_QUIT")), wxART_TOOLBAR),
      wxDefaultPosition, wxDefaultSize, wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON11"));
  btnQuit->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_QUIT")), wxART_TOOLBAR));
  btnQuit->SetMargins(wxSize(5, 5));
  FlexGridSizer2->Add(btnQuit, 1, wxEXPAND, 1);
  FlexGridSizer1->Add(FlexGridSizer2, 1, wxEXPAND, 0);
  SetSizer(FlexGridSizer1);
  MenuBar1 = new wxMenuBar();
  Menu1 = new wxMenu();
  MenuItem3 = new wxMenuItem(Menu1, ID_MENUITEM1, _("New scene"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem3);
  MenuItem4 = new wxMenuItem(Menu1, ID_MENUITEM2, _("Open..."), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem4);
  MenuItem7 =
      new wxMenuItem(Menu1, ID_MENUITEM5, _("Reload file\tF5"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem7);
  MenuItem9 = new wxMenuItem(Menu1, ID_MENUITEM7, _("Save..."), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem9);
  Menu1->AppendSeparator();
  MenuItem18 = new wxMenu();
  MenuItem8 = new wxMenuItem(
      MenuItem18, ID_MENUITEM6, _("a 3D model (any Assimp format)..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem18->Append(MenuItem8);
  MenuItem19 = new wxMenuItem(
      MenuItem18, ID_MENUITEM20, _("a PLY point cloud..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem18->Append(MenuItem19);
  mnuImportLAS = new wxMenuItem(
      MenuItem18, ID_MENUITEM25, _("a LAS LiDAR file..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem18->Append(mnuImportLAS);
  MenuItemImportImage = new wxMenuItem(
      MenuItem18, ID_MENUITEM_ImportImage, _("an image..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem18->Append(MenuItemImportImage);
  Menu1->Append(ID_MENUITEM19, _("Import"), MenuItem18, wxEmptyString);
  MenuItem20 = new wxMenu();
  MenuItem21 = new wxMenuItem(
      MenuItem20, ID_MENUITEM22, _("point clouds to PLY file..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem20->Append(MenuItem21);
  Menu1->Append(ID_MENUITEM21, _("Export"), MenuItem20, wxEmptyString);
  Menu1->AppendSeparator();
  MenuItem23 =
      new wxMenuItem(Menu1, ID_MENUITEM29, _("Previous file\tF7"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem23);
  MenuItem24 =
      new wxMenuItem(Menu1, ID_MENUITEM30, _("Next file\tF8"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem24);
  Menu1->AppendSeparator();
  MenuItem14 = new wxMenuItem(
      Menu1, ID_MENUITEM12, _("Take snapshot...\tF2"),
      _("Saves the current window image to a file"), wxITEM_NORMAL);
  Menu1->Append(MenuItem14);
  MenuItem22 = new wxMenuItem(
      Menu1, ID_MENUITEM23, _("High-resolution render to file..."), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(MenuItem22);
  mnuSceneStats =
      new wxMenuItem(Menu1, ID_MENUITEM18, _("Scene stats"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(mnuSceneStats);
  mnuPrintScene = new wxMenuItem(
      Menu1, ID_MENUITEM_PRINT_TEXT, _("Print as text to console"), wxEmptyString, wxITEM_NORMAL);
  Menu1->Append(mnuPrintScene);
  Menu1->AppendSeparator();
  MenuItem1 = new wxMenuItem(
      Menu1, idMenuQuit, _("Quit\tAlt-F4"), _("Quit the application"), wxITEM_NORMAL);
  Menu1->Append(MenuItem1);
  MenuBar1->Append(Menu1, _("&File"));
  Menu4 = new wxMenu();
  mnuSelectNone =
      new wxMenuItem(Menu4, ID_MENUITEM24, _("Select none"), wxEmptyString, wxITEM_NORMAL);
  Menu4->Append(mnuSelectNone);
  mnuSelectByClass =
      new wxMenuItem(Menu4, ID_MENUITEM26, _("Select by class..."), wxEmptyString, wxITEM_NORMAL);
  Menu4->Append(mnuSelectByClass);
  Menu4->AppendSeparator();
  mnuSelectionScale =
      new wxMenuItem(Menu4, ID_MENUITEM27, _("Re-scale selected..."), wxEmptyString, wxITEM_NORMAL);
  Menu4->Append(mnuSelectionScale);
  mnuSelectionDelete =
      new wxMenuItem(Menu4, ID_MENUITEM28, _("Delete selected"), wxEmptyString, wxITEM_NORMAL);
  Menu4->Append(mnuSelectionDelete);
  MenuBar1->Append(Menu4, _("&Edit"));
  Menu3 = new wxMenu();
  MenuItem6 =
      new wxMenuItem(Menu3, ID_MENUITEM4, _("Background color..."), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem6);
  MenuItem5 = new wxMenuItem(Menu3, ID_MENUITEM3, _("Options..."), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem5);
  Menu3->AppendSeparator();
  MenuItem17 = new wxMenu();
  mnuItemShowCloudOctrees = new wxMenuItem(
      MenuItem17, ID_MENUITEM15, _("Show/hide bounding boxes"), wxEmptyString, wxITEM_CHECK);
  MenuItem17->Append(mnuItemShowCloudOctrees);
  mnuItemChangeMaxPointsPerOctreeNode = new wxMenuItem(
      MenuItem17, ID_MENUITEM17, _("Change octree parameters..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem17->Append(mnuItemChangeMaxPointsPerOctreeNode);
  Menu3->Append(ID_MENUITEM16, _("Point clouds octrees..."), MenuItem17, wxEmptyString);
  Menu3->AppendSeparator();
  MenuItem13 =
      new wxMenuItem(Menu3, ID_MENUITEM11, _("Delete all objects"), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem13);
  MenuItem11 = new wxMenu();
  MenuItem12 = new wxMenuItem(
      MenuItem11, ID_MENUITEM9, _("Simple robot model"), wxEmptyString, wxITEM_NORMAL);
  MenuItem11->Append(MenuItem12);
  Menu3->Append(ID_MENUITEM8, _("Insert stock object"), MenuItem11, wxEmptyString);
  Menu3->AppendSeparator();
  MenuItem15 = new wxMenu();
  MenuItem10 =
      new wxMenuItem(MenuItem15, ID_MENUITEM10, _("Circular"), wxEmptyString, wxITEM_NORMAL);
  MenuItem15->Append(MenuItem10);
  MenuItem16 = new wxMenuItem(
      MenuItem15, ID_MENUITEM14, _("Arbitrary path..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem15->Append(MenuItem16);
  Menu3->Append(ID_MENUITEM13, _("Camera tracking"), MenuItem15, wxEmptyString);
  MenuBar1->Append(Menu3, _("&Tools"));
  Menu2 = new wxMenu();
  MenuItem2 = new wxMenuItem(
      Menu2, idMenuAbout, _("About\tF1"), _("Show info about this application"), wxITEM_NORMAL);
  Menu2->Append(MenuItem2);
  MenuBar1->Append(Menu2, _("&Help"));
  SetMenuBar(MenuBar1);
  StatusBar1 = new wxStatusBar(this, ID_STATUSBAR1, 0, _T("ID_STATUSBAR1"));
  int __wxStatusBarWidths_1[4] = {-10, -10, -4, -5};
  int __wxStatusBarStyles_1[4] = {wxSB_NORMAL, wxSB_NORMAL, wxSB_NORMAL, wxSB_NORMAL};
  StatusBar1->SetFieldsCount(4, __wxStatusBarWidths_1);
  StatusBar1->SetStatusStyles(4, __wxStatusBarStyles_1);
  SetStatusBar(StatusBar1);
  timLoadFileCmdLine.SetOwner(this, ID_TIMER1);
  timLoadFileCmdLine.Start(50, false);
  FlexGridSizer1->Fit(this);
  FlexGridSizer1->SetSizeHints(this);
  Center();

  using svf = _DSceneViewerFrame;

  Bind(wxEVT_BUTTON, &svf::OnNewScene, this, ID_BUTTON1);
  Bind(wxEVT_BUTTON, &svf::OnOpenFile, this, ID_BUTTON2);
  Bind(wxEVT_BUTTON, &svf::OnPrevious, this, ID_BUTTON3);
  Bind(wxEVT_BUTTON, &svf::OnNext, this, ID_BUTTON4);
  Bind(wxEVT_BUTTON, &svf::OnReload, this, ID_BUTTON5);
  Bind(wxEVT_BUTTON, &svf::OnMenuOptions, this, ID_BUTTON6);
  Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnOrthoClicked, this, ID_BUTTON7);
  Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnAutoplayClicked, this, ID_BUTTON8);
  Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnBtnRecordClicked, this, ID_BUTTON9);
  Bind(wxEVT_BUTTON, &svf::OnAbout, this, ID_BUTTON10);
  Bind(wxEVT_BUTTON, &svf::OnQuit, this, ID_BUTTON11);
  Bind(wxEVT_MENU, &svf::OnNewScene, this, ID_MENUITEM1);
  Bind(wxEVT_MENU, &svf::OnOpenFile, this, ID_MENUITEM2);
  Bind(wxEVT_MENU, &svf::OnReload, this, ID_MENUITEM5);
  Bind(wxEVT_MENU, &svf::OnMenuSave, this, ID_MENUITEM7);
  Bind(wxEVT_MENU, &svf::OnInsert3DS, this, ID_MENUITEM6);
  Bind(wxEVT_MENU, &svf::OnMenuItemImportPLYPointCloud, this, ID_MENUITEM20);
  Bind(wxEVT_MENU, &svf::OnmnuImportLASSelected, this, ID_MENUITEM25);
  Bind(wxEVT_MENU, &svf::OnmnuImportImageView, this, ID_MENUITEM_ImportImage);
  Bind(wxEVT_MENU, &svf::OnMenuItemExportPointsPLY, this, ID_MENUITEM22);
  Bind(wxEVT_MENU, &svf::OnPrevious, this, ID_MENUITEM29);
  Bind(wxEVT_MENU, &svf::OnNext, this, ID_MENUITEM30);
  Bind(wxEVT_MENU, &svf::OnMenuItem14Selected, this, ID_MENUITEM12);
  Bind(wxEVT_MENU, &svf::OnMenuItemHighResRender, this, ID_MENUITEM23);
  Bind(wxEVT_MENU, &svf::OnmnuSceneStatsSelected, this, ID_MENUITEM18);
  Bind(wxEVT_MENU, &svf::OnMenuPrintScene, this, ID_MENUITEM_PRINT_TEXT);
  Bind(wxEVT_MENU, &svf::OnQuit, this, idMenuQuit);
  Bind(wxEVT_MENU, &svf::OnmnuSelectNoneSelected, this, ID_MENUITEM24);
  Bind(wxEVT_MENU, &svf::OnmnuSelectByClassSelected, this, ID_MENUITEM26);
  Bind(wxEVT_MENU, &svf::OnmnuSelectionScaleSelected, this, ID_MENUITEM27);
  Bind(wxEVT_MENU, &svf::OnmnuSelectionDeleteSelected, this, ID_MENUITEM28);
  Bind(wxEVT_MENU, &svf::OnMenuBackColor, this, ID_MENUITEM4);
  Bind(wxEVT_MENU, &svf::OnMenuOptions, this, ID_MENUITEM3);
  Bind(wxEVT_MENU, &svf::OnmnuItemShowCloudOctreesSelected, this, ID_MENUITEM15);
  Bind(wxEVT_MENU, &svf::OnmnuItemChangeMaxPointsPerOctreeNodeSelected, this, ID_MENUITEM17);
  Bind(wxEVT_MENU, &svf::OnMenuDeleteAll, this, ID_MENUITEM11);
  Bind(wxEVT_MENU, &svf::OnMenuAddSICK, this, ID_MENUITEM9);
  Bind(wxEVT_MENU, &svf::OnStartCameraTravelling, this, ID_MENUITEM10);
  Bind(wxEVT_MENU, &svf::OnAbout, this, idMenuAbout);
  Bind(wxEVT_TIMER, &svf::OntimLoadFileCmdLineTrigger, this, ID_TIMER1);
  //*)

  Bind(wxEVT_MENU, &svf::OnMenuCameraTrackingArbitrary, this, ID_MENUITEM14);
  Bind(wxEVT_TIMER, &svf::OntimAutoplay, this, ID_TIMER_AUTOPLAY);
  Bind(wxEVT_TIMER, &svf::OnTravellingTrigger, this, ID_TRAVELLING_TIMER);

  // Create the wxCanvas object:
  m_canvas = new CMyGLCanvas(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
#if wxCHECK_VERSION(2, 9, 0)
  m_canvas->SetMinClientSize(wxSize(100, 100));
#endif
  m_canvas->setMinimumZoom(0.1);
  FlexGridSizer1->Add(m_canvas, 1, wxEXPAND, 0);

  // Load an empty scene:
  wxCommandEvent dummEvent;
  OnNewScene(dummEvent);

  m_autoplayTimer = std::make_unique<wxTimer>(this, ID_TIMER_AUTOPLAY);

  m_tTravelling.SetOwner(this, ID_TRAVELLING_TIMER);

  m_dlg_tracking = new CDlgCamTracking(this);

  Maximize();
}

_DSceneViewerFrame::~_DSceneViewerFrame()
{
  theWindow = nullptr;

  //(*Destroy(_DSceneViewerFrame)
  //*)
}

void _DSceneViewerFrame::OnQuit(wxCommandEvent&) { Close(); }
void _DSceneViewerFrame::OnAbout(wxCommandEvent&)
{
  mrpt::gui::show_mrpt_about_box_wxWidgets(this, "SceneViewer3D");
}

void _DSceneViewerFrame::OnNewScene(wxCommandEvent& event)
{
  auto openGLSceneRef = m_canvas->getOpenGLSceneRef();
  openGLSceneRef->clear();

  // Default file name:
  loadedFileName = "no-name.3Dscene";
  updateTitle();

  {
    mrpt::opengl::CGridPlaneXY::Ptr obj =
        mrpt::opengl::CGridPlaneXY::Create(-50, 50, -50, 50, 0, 1);
    obj->setColor(0.3f, 0.3f, 0.3f);
    openGLSceneRef->insert(obj);
  }

  m_canvas->setCameraPointing(0.0f, 0.0f, 0.0f);
  m_canvas->setZoomDistance(20.0f);
  m_canvas->setElevationDegrees(45.0f);
  m_canvas->setAzimuthDegrees(45.0f);

  openGLSceneRef->insert(stock_objects::CornerXYZ());

  // Add a clone viewport:
  Viewport::Ptr vi = openGLSceneRef->createViewport("clone");
  vi->setViewportPosition(0.05, 0.05, 0.2, 0.2);
  vi->setCloneView("main");
  vi->setCloneCamera(true);
  vi->setBorderSize(1);

  Refresh(false);
}

void _DSceneViewerFrame::OnOpenFile(wxCommandEvent& event)
{
  wxString caption = wxT("Choose a file to open");
  wxString wildcard = wxT("3D scene files (*.3Dscene)|*.3Dscene|All files (*.*)|*.*");

  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

  wxString defaultFilename;
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();

    loadFromFile(std::string(fileName.mb_str()));
  }
}

void _DSceneViewerFrame::loadFromFile(const std::string& fil, bool isInASequence)
{
  WX_START_TRY
  try
  {
    // Save the path
    saveLastUsedDirectoryToCfgFile(fil);

    CFileGZInputStream f(fil);

    const auto oldCanvasCamera = m_canvas->cameraParams();

    static mrpt::system::CTicTac tictac;
    auto& openGLSceneRef = m_canvas->getOpenGLSceneRef();
    {
      std::lock_guard<std::mutex> lock(critSec_UpdateScene);

      tictac.Tic();

      openGLSceneRef->clear();
      mrpt::serialization::archiveFrom(f) >> *openGLSceneRef;
    }

    double timeToLoad = tictac.Tac();

    if (openGLSceneRef->viewportsCount() == 0 || !openGLSceneRef->getViewport("main") ||
        openGLSceneRef->getViewport("main")->size() == 0)
    {
      wxMessageBox(_("File is empty or format unrecognized."), _("Warning"), wxOK, this);
      btnAutoplay->SetValue(false);
    }

    // try to autoguess the clip distances:
    const auto sceneBbox = openGLSceneRef->getBoundingBox();
    const double sceneMaxScale = std::max<double>(1000.0, (sceneBbox.max - sceneBbox.min).norm());
    openGLSceneRef->getViewport()->setViewportClipDistances(0.1, 5.0 * sceneMaxScale);

    // Change the camera if necessary:
    if (openGLSceneRef->followCamera())
    {
      Viewport::Ptr view = openGLSceneRef->getViewport("main");
      ASSERTMSG_(view, "ERROR: there is no 'main' viewport in the 3D scene!");

      CCamera::Ptr cam = openGLSceneRef->getByClass<CCamera>();

      bool camIsCCameraObj = cam ? true : false;
      if (!camIsCCameraObj) cam = CCamera::Create(view->getCamera());

      m_canvas->setCameraPointing(
          cam->getPointingAtX(), cam->getPointingAtY(), cam->getPointingAtZ());

      // If it's not loaded thru arrow keys or a timed sequence, take
      // camera:
      if (!isInASequence || !freeCameraAlways || !freeCameraAlwaysNoAzimuth)
      {
        if (!freeCameraAlwaysNoAzimuth)
        {
          m_canvas->setZoomDistance(cam->getZoomDistance());
          m_canvas->setElevationDegrees(cam->getElevationDegrees());
        }
        m_canvas->setAzimuthDegrees(cam->getAzimuthDegrees());
      }

      // Remove the camera from the object:
      if (camIsCCameraObj) openGLSceneRef->removeObject(cam);
    }
    else
    {
      m_canvas->setCameraParams(oldCanvasCamera);
    }

    loadedFileName = fil;

    if (showFileNameInViewport)
    {
      openGLSceneRef->getViewport()->addTextMessage(20, 20, extractFileName(loadedFileName));
    }

    // Set the file name as window title:
    updateTitle();

    theWindow->StatusBar1->SetStatusText((format("File loaded in %.03fs", timeToLoad).c_str()), 0);

    Refresh(false);
    Update();
  }
  catch (const std::exception&)
  {
    btnAutoplay->SetValue(false);
    throw;
  }
  WX_END_TRY
}

void _DSceneViewerFrame::updateTitle()
{
  SetTitle(
      (format(
           "SceneViewer3D - Part of the MRPT project [%s]",
           (extractFileName(loadedFileName) + string(".") + extractFileExtension(loadedFileName))
               .c_str())
           .c_str()));
}

void _DSceneViewerFrame::OntimLoadFileCmdLineTrigger(wxTimerEvent&)
{
  timLoadFileCmdLine.Stop();  // One shot only.
  // Open file if passed by the command line:
  if (!global_fileToOpen.empty())
  {
    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
      {
        auto obj3D = mrpt::opengl::CAssimpModel::Create();
        obj3D->loadScene(global_fileToOpen);
        // obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg,
        // 0._deg, 90.0_deg));
        m_canvas->getOpenGLSceneRef()->insert(obj3D);

        m_canvas->Refresh();
      }
      catch (const std::exception& e)
      {
        std::cerr << mrpt::exception_to_str(e) << std::endl;
        wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
      }
    }
  }
}

void _DSceneViewerFrame::OnbtnAutoplayClicked(wxCommandEvent& event)
{
  if (btnAutoplay->GetValue()) m_autoplayTimer->Start(5, true);  // One-shot:
}

void _DSceneViewerFrame::OntimAutoplay(wxTimerEvent& event)
{
  // Load next file:
  wxKeyEvent dummyEvent;
  dummyEvent.m_keyCode = WXK_RIGHT;
  m_canvas->OnCharCustom(dummyEvent);

  // Continue?
  if (btnAutoplay->GetValue()) m_autoplayTimer->Start(delayBetweenAutoplay, true);  // One-shot:
}

void _DSceneViewerFrame::OnMenuBackColor(wxCommandEvent& event)
{
  wxColourData colourData;
  wxColour color;

  auto bkCol = m_canvas->getOpenGLSceneRef()->getViewport()->getCustomBackgroundColor();

  color.Set((int)(255 * bkCol.R), (int)(255 * bkCol.G), (int)(255 * bkCol.B));

  colourData.SetColour(color);
  colourData.SetChooseFull(true);

  wxColourDialog colDial(this, &colourData);

  if (wxID_OK == colDial.ShowModal())
  {
    wxColour col = colDial.GetColourData().GetColour();

    m_canvas->getOpenGLSceneRef()->getViewport()->setCustomBackgroundColor(
        {col.Red() / 255.0f, col.Green() / 255.0f, col.Blue() / 255.0f});

    int w, h;
    m_canvas->GetSize(&w, &h);
    RefreshRect(wxRect(0, 0, w, h), false);
  }
}

// See options:
void _DSceneViewerFrame::OnMenuOptions(wxCommandEvent& event)
{
  CDialogOptions dial(this);

  // Put current options:
  dial.cbViewFileName->SetValue(showFileNameInViewport);
  dial.cbFreeCamera->SetValue(freeCameraAlways);
  dial.cbFreeCameraNoAzimuth->SetValue(freeCameraAlwaysNoAzimuth);
  dial.edDelay->SetValue(delayBetweenAutoplay);

  // Show
  if (dial.ShowModal() == wxID_OK)
  {
    // If OK, get new options:
    showFileNameInViewport = dial.cbViewFileName->GetValue();
    freeCameraAlways = dial.cbFreeCamera->GetValue();
    freeCameraAlwaysNoAzimuth = dial.cbFreeCameraNoAzimuth->GetValue();
    delayBetweenAutoplay = dial.edDelay->GetValue();
    m_canvas->Refresh(false);
  }
}

void _DSceneViewerFrame::OnPrevious(wxCommandEvent& event)
{
  wxKeyEvent evn;
  evn.m_keyCode = WXK_LEFT;
  m_canvas->OnChar(evn);
}

void _DSceneViewerFrame::OnNext(wxCommandEvent& event)
{
  wxKeyEvent evn;
  evn.m_keyCode = WXK_RIGHT;
  m_canvas->OnChar(evn);
}

void _DSceneViewerFrame::OnClose(wxCloseEvent& event) {}
void _DSceneViewerFrame::OnBtnRecordClicked(wxCommandEvent& event)
{
  if (btnCapture->GetValue())
  {
    // Starting recording:
    wxDirDialog dirDialog(
        this, _("Choose the directory where to save the screenshots:"),
        extractFileDirectory(loadedFileName).c_str(), 0, wxDefaultPosition);

    if (dirDialog.ShowModal() == wxID_OK)
    {
      capturingDir = string(dirDialog.GetPath().mb_str());
      isCapturing = true;
      captureCount = 0;
      btnCapture->SetValue(true);
    }
    else
    {
      btnCapture->SetValue(false);
    }
  }
  else
  {
    isCapturing = false;
  }
}

void _DSceneViewerFrame::OnbtnOrthoClicked(wxCommandEvent& event)
{
  bool ortho = btnOrtho->GetValue();
  m_canvas->setCameraProjective(!ortho);

  m_canvas->Refresh(false);
}

void _DSceneViewerFrame::OnReload(wxCommandEvent& event)
{
  if (fileExists(loadedFileName)) loadFromFile(loadedFileName);
}

void _DSceneViewerFrame::OnInsert3DS(wxCommandEvent& event)
{
  try
  {
    wxString caption = wxT("Choose a file to import");
    wxString wildcard =
        wxT("3D models (All Assimp "
            "formats)|*.dae;*.blend;*.3ds;*.ase;*.obj;*.ifc;*.xgl;.zgl;*."
            "ply;*.dxf;.lwo;*.lws;*.lxo;*.stl;*.x;*.ac;*.ms3d;*.cob;.scn;*."
            "bvh;*.csm;*.xml;*.irrmesh;*.irr;*.mdl;*.md2;*.md3;*.pk3;*.mdc;"
            "*.md5*;*.smd;.vta;*.m3;*.3d;*.b3d;*.q3d;*.q3s;*.nff;*.nff;*."
            "off;*.raw;*.ter;*.mdl;*.hmp;*.ndo;|All files (*.*)|*.*");
    wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
    wxString defaultFilename;

    wxFileDialog dialog(
        this, caption, defaultDir, defaultFilename, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);

    if (dialog.ShowModal() != wxID_OK) return;

    wxString fileName = dialog.GetPath();
    std::string fil = string(fileName.mb_str());

    saveLastUsedDirectoryToCfgFile(fil);

    mrpt::opengl::CAssimpModel::Ptr obj3D = mrpt::opengl::CAssimpModel::Create();
    obj3D->loadScene(fil);
    // obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg,
    // 0._deg, 90.0_deg));
    m_canvas->getOpenGLSceneRef()->insert(obj3D);

    m_canvas->Refresh();
  }
  catch (const std::exception& e)
  {
    std::cerr << mrpt::exception_to_str(e) << std::endl;
    btnAutoplay->SetValue(false);
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
  catch (...)
  {
    btnAutoplay->SetValue(false);
    wxMessageBox(_("Runtime error!"), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnMenuSave(wxCommandEvent& event)
{
  try
  {
    wxString caption = wxT("Save scene to file");
    wxString wildcard = wxT("3Dscene files (*.3Dscene)|*.3Dscene|All files (*.*)|*.*");
    wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
    wxString defaultFilename;

    wxFileDialog dialog(
        this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

    if (dialog.ShowModal() != wxID_OK) return;

    wxString fileName = dialog.GetPath();

    CFileGZOutputStream fo(string(fileName.mb_str()));
    mrpt::serialization::archiveFrom(fo) << *m_canvas->getOpenGLSceneRef();
  }
  catch (const std::exception& e)
  {
    std::cerr << mrpt::exception_to_str(e) << std::endl;
    btnAutoplay->SetValue(false);
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
  catch (...)
  {
    btnAutoplay->SetValue(false);
    wxMessageBox(_("Runtime error!"), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnTravellingTrigger(wxTimerEvent& event)
{
  try
  {
    auto openGLSceneRef = m_canvas->getOpenGLSceneRef();
    if ((openGLSceneRef->viewportsCount() == 0) || !openGLSceneRef->getViewport("main") ||
        (openGLSceneRef->getViewport("main")->size() == 0))
    {
      wxMessageBox(_("Canvas is empty"), _("Warning"), wxOK, this);
    }
    else
    {
      // Change the camera
      Viewport::Ptr view = openGLSceneRef->getViewport("main");

      if (!view)
        THROW_EXCEPTION(
            "Fatal error: there is no 'main' viewport in the 3D "
            "scene!");

      // Do (in Spanish) "travelling":
      if (m_travelling_is_arbitrary)
      {
        // ===============================
        // Arbitrary camera tracking
        // ===============================
        if (m_dlg_tracking->m_poses.empty())
        {
          m_tTravelling.Stop();
          return;
        }

        mrpt::Clock::time_point t = m_dlg_tracking->m_poses.begin()->first +
                                    (mrpt::Clock::now() - m_travelling_start_time.value());
        bool valid = false;
        CPose3D p;
        while (!valid && t < m_dlg_tracking->m_poses.rbegin()->first)
        {
          m_dlg_tracking->m_poses.interpolate(t, p, valid);
          if (!valid)
          {
            using namespace std::chrono_literals;
            auto At = 100ms;
            t += At;
            m_travelling_start_time.value() -= At;
          }
        }

        if (valid)
          m_canvas->setCameraPointing(p.x(), p.y(), p.z());
        else
        {
          // end of path:
          m_tTravelling.Stop();
          return;
        }
      }
      else
      {
        // ===============================
        // Circular camera tracking
        // ===============================
        double step = atof(iniFile->read_string("Spherical travelling", "Step", "5").c_str());

        if ((m_canvas->getAzimuthDegrees() + step) < maxv)
          m_canvas->setAzimuthDegrees(m_canvas->getAzimuthDegrees() + step / 10.0);
        else
          m_tTravelling.Stop();
      }

      Refresh(false);
    }
  }
  catch (const std::exception& e)
  {
    std::cerr << mrpt::exception_to_str(e) << std::endl;
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
  catch (...)
  {
    wxMessageBox(_("Runtime error!"), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnStartCameraTravelling(wxCommandEvent& event)
{
  try
  {
    auto openGLSceneRef = m_canvas->getOpenGLSceneRef();
    if ((openGLSceneRef->viewportsCount() == 0) || !openGLSceneRef->getViewport("main") ||
        (openGLSceneRef->getViewport("main")->size() == 0))
    {
      wxMessageBox(_("Canvas is empty"), _("Warning"), wxOK, this);
    }
    else
    {
      // Change the camera
      Viewport::Ptr view = openGLSceneRef->getViewport("main");

      if (!view)
        THROW_EXCEPTION(
            "Fatal error: there is no 'main' viewport in the 3D "
            "scene!");

      double target_x = atof(iniFile->read_string("Spherical travelling", "X", "0").c_str());
      double target_y = atof(iniFile->read_string("Spherical travelling", "Y", "0").c_str());
      double target_z = atof(iniFile->read_string("Spherical travelling", "Z", "0").c_str());

      double zoom =
          atof(iniFile->read_string("Spherical travelling", "Zoom distance", "25").c_str());
      double elevation =
          atof(iniFile->read_string("Spherical travelling", "Elevation degrees", "15").c_str());
      double azimuth =
          atof(iniFile->read_string("Spherical travelling", "Azimuth degrees", "90").c_str());

      double min_value =
          atof(iniFile->read_string("Spherical travelling", "Min value", "90").c_str());
      double max_value =
          atof(iniFile->read_string("Spherical travelling", "Max value", "90").c_str());

      m_canvas->setCameraPointing(target_x, target_y, target_z);
      m_canvas->setZoomDistance(zoom);
      m_canvas->setElevationDegrees(elevation);
      m_canvas->setAzimuthDegrees(azimuth - min_value);

      maxv = azimuth + max_value;

      m_travelling_is_arbitrary = false;  // Circular
      m_tTravelling.Start(100);

      Refresh(false);
    }
  }
  catch (const std::exception& e)
  {
    std::cerr << mrpt::exception_to_str(e) << std::endl;
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
  catch (...)
  {
    wxMessageBox(_("Runtime error!"), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnClose1(wxCloseEvent& event) {}
void _DSceneViewerFrame::OnMenuAddSICK(wxCommandEvent& event)
{
  try
  {
    mrpt::opengl::CSetOfObjects::Ptr obj = mrpt::opengl::stock_objects::RobotPioneer();
    m_canvas->getOpenGLSceneRef()->insert(obj);
    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);
  }
  catch (...)
  {
    wxMessageBox(_("Runtime error!"), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnMenuDeleteAll(wxCommandEvent& event)
{
  m_canvas->getOpenGLSceneRef()->clear();
  m_canvas->Refresh();
}

void _DSceneViewerFrame::OnMenuItem14Selected(wxCommandEvent& event)
{
  m_canvas->Refresh();
  wxApp::GetInstance()->Yield();

  wxClientDC dc(this);

  int w, h;
  dc.GetSize(&w, &h);

  // Save image directly from OpenGL
  CImage frame(w, h, CH_RGB);

  glPixelStorei(GL_PACK_ALIGNMENT, 1);
  glPixelStorei(GL_PACK_ROW_LENGTH, 0);

  glReadBuffer(GL_FRONT);
  glReadPixels(0, 0, w, h, GL_BGR_EXT, GL_UNSIGNED_BYTE, frame(0, 0));
  frame.flipVertical();

  // Save:
  wxString caption = wxT("Save snapshot to file");
  wxString wildcard = wxT("Image files (*.png)|*.png|All files (*.*)|*.*");
  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
  wxString defaultFilename = wxT("snapshot.png");

  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() != wxID_OK) return;

  bool savedOk = frame.saveToFile(std::string(dialog.GetPath().mb_str()));
  ASSERT_(savedOk);
}

void _DSceneViewerFrame::OnMenuCameraTrackingArbitrary(wxCommandEvent& event)
{
  m_dlg_tracking->Show();
}

void _DSceneViewerFrame::OnmnuItemChangeMaxPointsPerOctreeNodeSelected(wxCommandEvent& event)
{
  wxString sRet1 = wxGetTextFromUser(
      _("Max. number of points in an octree node before split:"), _("Enter new value"),
      wxString::Format(_("%e"), (double)mrpt::global_settings::OCTREE_RENDER_MAX_POINTS_PER_NODE()),
      this);

  wxString sRet2 = wxGetTextFromUser(
      _("Max. density of points in each octree (points/pixel^2):"), _("Enter new value"),
      wxString::Format(
          _("%e"), (double)mrpt::global_settings::OCTREE_RENDER_MAX_DENSITY_POINTS_PER_SQPIXEL()),
      this);

  double N1, N2;
  if (sRet1.ToCDouble(&N1) && sRet2.ToCDouble(&N2))
  {
    mrpt::global_settings::OCTREE_RENDER_MAX_POINTS_PER_NODE(N1);
    mrpt::global_settings::OCTREE_RENDER_MAX_DENSITY_POINTS_PER_SQPIXEL(N2);

    // Redo the octrees:
    clear_all_octrees_in_scene();
    Refresh(false);

    wxCommandEvent dumm;  // Redraw bounding-boxes:
    OnmnuItemShowCloudOctreesSelected(dumm);
  }
  else
    wxMessageBox(_("Invalid number!"));
}

void func_clear_octrees(const mrpt::opengl::CRenderizable::Ptr& o)
{
  if (IS_CLASS(*o, CPointCloud))
  {
    CPointCloud::Ptr obj = std::dynamic_pointer_cast<CPointCloud>(o);
    obj->octree_mark_as_outdated();
  }
  else if (IS_CLASS(*o, CPointCloudColoured))
  {
    CPointCloudColoured::Ptr obj = std::dynamic_pointer_cast<CPointCloudColoured>(o);
    obj->octree_mark_as_outdated();
  }
}

void _DSceneViewerFrame::clear_all_octrees_in_scene()
{
  {
    std::lock_guard<std::mutex> lock(critSec_UpdateScene);
    m_canvas->getOpenGLSceneRef()->visitAllObjects(&func_clear_octrees);
  }
}

struct TSceneStats
{
  void clear()
  {
    nPoints = 0;
    nObjects = 0;
    nOctreeVisible = 0;
    nOctreeTotal = 0;
  }

  size_t nPoints;
  size_t nObjects;
  size_t nOctreeVisible, nOctreeTotal;
};

TSceneStats sceneStats;

void func_gather_stats(const mrpt::opengl::CRenderizable::Ptr& o)
{
  sceneStats.nObjects++;

  if (IS_CLASS(*o, CPointCloud))
  {
    CPointCloud::Ptr obj = std::dynamic_pointer_cast<CPointCloud>(o);
    sceneStats.nPoints += obj->size();
    sceneStats.nOctreeVisible += obj->octree_get_visible_nodes();
    sceneStats.nOctreeTotal += obj->octree_get_node_count();
  }
  else if (IS_CLASS(*o, CPointCloudColoured))
  {
    CPointCloudColoured::Ptr obj = std::dynamic_pointer_cast<CPointCloudColoured>(o);
    sceneStats.nPoints += obj->size();
    sceneStats.nOctreeVisible += obj->octree_get_visible_nodes();
    sceneStats.nOctreeTotal += obj->octree_get_node_count();
  }
}

void _DSceneViewerFrame::OnMenuPrintScene(wxCommandEvent&)
{
  try
  {
    auto d = m_canvas->getOpenGLSceneRef()->asYAML();
    d.printAsYAML(std::cout);
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

// Gather stats on the scene:
void _DSceneViewerFrame::OnmnuSceneStatsSelected(wxCommandEvent&)
{
  try
  {
    wxBusyCursor wait;
    sceneStats.clear();

    {
      std::lock_guard<std::mutex> lock(critSec_UpdateScene);
      m_canvas->getOpenGLSceneRef()->visitAllObjects(&func_gather_stats);
    }

    std::stringstream ss;
    ss << "Number of objects: " << sceneStats.nObjects << endl
       << format("Overall points (in point clouds): %e", double(sceneStats.nPoints)) << endl
       << "Total octree nodes   (in point clouds): " << sceneStats.nOctreeTotal << endl
       << "Visible octree nodes (in point clouds): " << sceneStats.nOctreeVisible << endl;

    wxMessageBox(ss.str().c_str(), _("Scene statistics"));
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

static const std::string name_octrees_bb_globj = "__SceneViewer3D_gl_octree_bb__";
CSetOfObjects::Ptr aux_gl_octrees_bb;

void func_get_octbb(const mrpt::opengl::CRenderizable::Ptr& o)
{
  if (IS_CLASS(*o, CPointCloud))
  {
    CPointCloud::Ptr obj = std::dynamic_pointer_cast<CPointCloud>(o);
    CSetOfObjects::Ptr new_bb = std::make_shared<CSetOfObjects>();
    obj->octree_get_graphics_boundingboxes(*new_bb);
    aux_gl_octrees_bb->insert(new_bb);
  }
  else if (IS_CLASS(*o, CPointCloudColoured))
  {
    CPointCloudColoured::Ptr obj = std::dynamic_pointer_cast<CPointCloudColoured>(o);
    CSetOfObjects::Ptr new_bb = std::make_shared<CSetOfObjects>();
    obj->octree_get_graphics_boundingboxes(*new_bb);
    aux_gl_octrees_bb->insert(new_bb);
  }
}

// Show/hide the octree bounding boxes of the point clouds:
void _DSceneViewerFrame::OnmnuItemShowCloudOctreesSelected(wxCommandEvent& event)
{
  const bool show_hide = mnuItemShowCloudOctrees->IsChecked();

  try
  {
    wxBusyCursor wait;

    {
      auto openGLSceneRef = m_canvas->getOpenGLSceneRef();
      std::lock_guard<std::mutex> lock(critSec_UpdateScene);
      openGLSceneRef->visitAllObjects(&func_gather_stats);

      CSetOfObjects::Ptr gl_octrees_bb;

      // Get object from scene, or creat upon first usage:
      {
        CRenderizable::Ptr obj = openGLSceneRef->getByName(name_octrees_bb_globj);
        if (obj)
          gl_octrees_bb = std::dynamic_pointer_cast<CSetOfObjects>(obj);
        else
        {
          gl_octrees_bb = std::make_shared<CSetOfObjects>();
          gl_octrees_bb->setName(name_octrees_bb_globj);
          openGLSceneRef->insert(gl_octrees_bb);
        }
      }

      // Show or hide, clear first anyway:
      gl_octrees_bb->clear();

      if (show_hide)
      {
        // Show:
        aux_gl_octrees_bb = gl_octrees_bb;

        openGLSceneRef->visitAllObjects(func_get_octbb);

        aux_gl_octrees_bb.reset();
      }
    }

    Refresh(false);
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

// ----------------------------------------------------------
// Import a point cloud from the PLY file format
// ----------------------------------------------------------
void _DSceneViewerFrame::OnMenuItemImportPLYPointCloud(wxCommandEvent& event)
{
  try
  {
    wxFileDialog dialog(
        this, _("Choose the PLY file to import"),
        (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), _("*.ply"),
        _("PLY files (*.ply, *.PLY)|*.ply;*.PLY|All files (*.*)|*.*"),
        wxFD_OPEN | wxFD_FILE_MUST_EXIST);

    if (dialog.ShowModal() != wxID_OK) return;

    const std::string fil = string(dialog.GetPath().mb_str());
    saveLastUsedDirectoryToCfgFile(fil);

    CDlgPLYOptions dlgPLY(this);
    if (dlgPLY.ShowModal() != wxID_OK) return;

    opengl::CPointCloud::Ptr gl_points;
    opengl::CPointCloudColoured::Ptr gl_points_col;
    mrpt::opengl::PLY_Importer* ply_obj = nullptr;

    if (dlgPLY.rbClass->GetSelection() == 0)
    {
      gl_points = std::make_shared<opengl::CPointCloud>();
      ply_obj = gl_points.get();
    }
    else
    {
      gl_points_col = std::make_shared<opengl::CPointCloudColoured>();
      ply_obj = gl_points_col.get();
    }

    std::vector<std::string> file_comments, file_info;

    bool res;
    {
      wxBusyCursor busy;
      res = ply_obj->loadFromPlyFile(fil, &file_comments, &file_info);
    }
    if (!res)
    {
      wxMessageBox(
          ply_obj->getLoadPLYErrorString(), "Error loading or parsing the PLY file", wxOK, this);
    }
    else
    {
      auto& openGLSceneRef = m_canvas->getOpenGLSceneRef();
      // Set the point cloud as the only object in scene:
      openGLSceneRef = std::make_shared<opengl::Scene>();

      if (dlgPLY.cbXYGrid->GetValue())
      {
        mrpt::opengl::CGridPlaneXY::Ptr obj =
            mrpt::opengl::CGridPlaneXY::Create(-50, 50, -50, 50, 0, 1);
        obj->setColor(0.3f, 0.3f, 0.3f);
        openGLSceneRef->insert(obj);
      }

      if (dlgPLY.cbXYZ->GetValue())
        openGLSceneRef->insert(mrpt::opengl::stock_objects::CornerXYZ());

      double ptSize;
      dlgPLY.cbPointSize->GetStringSelection().ToCDouble(&ptSize);
      if (gl_points) gl_points->setPointSize(ptSize);
      if (gl_points_col) gl_points_col->setPointSize(ptSize);

      if (gl_points)
      {
        switch (dlgPLY.rbIntFromXYZ->GetSelection())
        {
          case 1:
            gl_points->enableColorFromX();
            break;
          case 2:
            gl_points->enableColorFromY();
            break;
          case 3:
            gl_points->enableColorFromZ();
            break;
        };
      }

      TPose3D ptCloudPose(0, 0, 0, 0, 0, 0);

      dlgPLY.edYaw->GetValue().ToCDouble(&ptCloudPose.yaw);
      dlgPLY.edPitch->GetValue().ToCDouble(&ptCloudPose.pitch);
      dlgPLY.edRoll->GetValue().ToCDouble(&ptCloudPose.roll);
      ptCloudPose.yaw = DEG2RAD(ptCloudPose.yaw);
      ptCloudPose.pitch = DEG2RAD(ptCloudPose.pitch);
      ptCloudPose.roll = DEG2RAD(ptCloudPose.roll);

      if (gl_points) gl_points->setPose(CPose3D(ptCloudPose));
      if (gl_points_col) gl_points_col->setPose(CPose3D(ptCloudPose));

      // Insert point cloud into scene:
      if (gl_points) openGLSceneRef->insert(gl_points);
      if (gl_points_col) openGLSceneRef->insert(gl_points_col);

      m_canvas->setCameraPointing(0.0f, 0.0f, 0.0f);
      m_canvas->setZoomDistance(4.0f);
      m_canvas->setAzimuthDegrees(45.0f);
      m_canvas->setElevationDegrees(30.0f);

      loadedFileName = std::string("Imported_") + fil + std::string(".3Dscene");
      updateTitle();

      Refresh(false);

      string sC, sI;
      mrpt::system::stringListAsString(file_comments, sC);
      mrpt::system::stringListAsString(file_info, sI);
      wxMessageBox(
          (format(
               "Comments:\n--------------------\n%s\nObject "
               "info:\n--------------------\n%s",
               sC.c_str(), sI.c_str())
               .c_str()),
          _("File info"), wxOK, this);
    }
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

struct visitor_export_PLY
{
  const string& filename;
  unsigned int& count;

  visitor_export_PLY(const string& fil, unsigned int& counter) : filename(fil), count(counter) {}

  void operator()(const mrpt::opengl::CRenderizable::Ptr& obj)
  {
    if (IS_CLASS(*obj, CPointCloud))
    {
      CPointCloud::Ptr o = std::dynamic_pointer_cast<CPointCloud>(obj);
      o->saveToPlyFile(format("%s_%03u.ply", filename.c_str(), ++count));
    }
    else if (IS_CLASS(*obj, CPointCloudColoured))
    {
      CPointCloudColoured::Ptr o = std::dynamic_pointer_cast<CPointCloudColoured>(obj);
      o->saveToPlyFile(format("%s_%03u.ply", filename.c_str(), ++count));
    }
  }
};

// ----------------------------------------------------------
// Export point clouds to the PLY file format
// ----------------------------------------------------------
void _DSceneViewerFrame::OnMenuItemExportPointsPLY(wxCommandEvent& event)
{
  try
  {
    wxMessageBox(
        _("Each point cloud object in the scene will be exported as a "
          "separate PLY file."),
        _("Notice"));

    wxFileDialog dialog(
        this, _("Choose the target PLY filename"),
        (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), _("*.ply"),
        _("PLY files (*.ply, *.PLY)|*.ply;*.PLY|All files (*.*)|*.*"),
        wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

    if (dialog.ShowModal() != wxID_OK) return;

    const std::string fil = string(dialog.GetPath().mb_str());
    saveLastUsedDirectoryToCfgFile(fil);

    // Go thru all point clouds and save them:
    unsigned int counter = 0;
    visitor_export_PLY visitor(fil, counter);

    {
      wxBusyCursor busy;
      m_canvas->getOpenGLSceneRef()->visitAllObjects(visitor);
    }

    wxMessageBox(
        wxString::Format(_("%u point cloud(s) exported to PLY files."), counter), _("Result"));
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

// Off-screen high-resolution render to image file:
void _DSceneViewerFrame::OnMenuItemHighResRender(wxCommandEvent& event)
{
  try
  {
    wxFileDialog dialog(
        this, _("Choose target image file"),
        (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), _("render.png"),
        _("Image files "
          "(*.png,*.tif,*.jpg,...)|*.png;*.tif;*.jpg;*.bmp|All "
          "files (*.*)|*.*"),
        wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
    if (dialog.ShowModal() != wxID_OK) return;

    const std::string sTargetFil = string(dialog.GetPath().mb_str());
    saveLastUsedDirectoryToCfgFile(sTargetFil);

    {
      wxBusyCursor busy;

      // It seems wxGetNumberFromUser() leads to a crash with wxGTK
      // (tested with wx2.8.11)
      // So use wxGetTextFromUser() instead
      wxString ssW =
          wxGetTextFromUser(_("Render image size"), _("Width in pixels:"), _("1024"), this);
      std::string sW = std::string(ssW.mb_str());
      const long width = atoi(sW.c_str());

      wxString ssH =
          wxGetTextFromUser(_("Render image size"), _("Height in pixels:"), _("768"), this);
      std::string sH = std::string(ssH.mb_str());
      const long height = atoi(sH.c_str());

      CFBORender::Parameters params;
      params.width = width;
      params.height = height;
      params.create_EGL_context = false;  // dont, will reuse existing one

      CFBORender render(params);
      CImage frame(width, height, CH_RGB);

      // render the scene
      render.render_RGB(*m_canvas->getOpenGLSceneRef(), frame);

      bool savedOk = frame.saveToFile(sTargetFil);
      ASSERT_(savedOk);
    }
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

// Select: none
void _DSceneViewerFrame::OnmnuSelectNoneSelected(wxCommandEvent& event)
{
  m_selected_gl_objects.clear();
}

class OpenGlObjectsFilterVirtual
{
 public:
  OpenGlObjectsFilterVirtual(std::vector<mrpt::opengl::CRenderizable::Ptr>& out_list) :
      m_out_list(out_list)
  {
  }

  virtual void operator()(const mrpt::opengl::CRenderizable::Ptr& obj)
  {
    if (checkObj(obj)) m_out_list.push_back(obj);
  }

  std::vector<mrpt::opengl::CRenderizable::Ptr>& m_out_list;

 protected:
  virtual bool checkObj(const mrpt::opengl::CRenderizable::Ptr& obj) = 0;
};

class OpenGlObjectsFilter_ByClass : public OpenGlObjectsFilterVirtual
{
 public:
  OpenGlObjectsFilter_ByClass(
      std::vector<mrpt::opengl::CRenderizable::Ptr>& out_list,
      const vector<const TRuntimeClassId*>& selected_classes) :
      OpenGlObjectsFilterVirtual(out_list), m_selected_classes(selected_classes)
  {
  }

 protected:
  bool checkObj(const mrpt::opengl::CRenderizable::Ptr& obj) override
  {
    for (auto m_selected_classe : m_selected_classes)
    {
      if (obj->GetRuntimeClass() == m_selected_classe) return true;
    }
    return false;
  }

  const vector<const TRuntimeClassId*>& m_selected_classes;
};

// Select: by class
void _DSceneViewerFrame::OnmnuSelectByClassSelected(wxCommandEvent& event)
{
  static bool init_list = true;
  static wxArrayString glClassNames;
  if (init_list)
  {
    init_list = false;
    vector<const TRuntimeClassId*> all_mrpt_classes = mrpt::rtti::getAllRegisteredClasses();
    for (auto& all_mrpt_classe : all_mrpt_classes)
      if (all_mrpt_classe->derivedFrom(CLASS_ID(CRenderizable)))
        glClassNames.Add(all_mrpt_classe->className);
  }

  wxArrayInt selections;
#if wxCHECK_VERSION(2, 9, 0)
  wxGetSelectedChoices(selections, _("Select by class:"), _("Select objects"), glClassNames, this);
#else
  wxGetMultipleChoices(selections, _("Select by class:"), _("Select objects"), glClassNames, this);
#endif

  // Build list of classes IDs:
  vector<const TRuntimeClassId*> selected_classes;
  for (size_t i = 0; i < selections.Count(); i++)
  {
    const std::string sName = std::string(glClassNames[selections[i]].mb_str());
    selected_classes.push_back(mrpt::rtti::findRegisteredClass(sName));
  }

  // Go thru objects and do filter:
  OpenGlObjectsFilter_ByClass filter(m_selected_gl_objects, selected_classes);
  m_canvas->getOpenGLSceneRef()->visitAllObjects(filter);

  theWindow->StatusBar1->SetStatusText(
      (mrpt::format("%u objects selected", static_cast<unsigned int>(m_selected_gl_objects.size()))
           .c_str()),
      0);
}

// On selection: delete
void _DSceneViewerFrame::OnmnuSelectionDeleteSelected(wxCommandEvent& event)
{
  for (auto& m_selected_gl_object : m_selected_gl_objects) m_selected_gl_object.reset();
  Refresh(false);
}

// On selection: re-scale
void _DSceneViewerFrame::OnmnuSelectionScaleSelected(wxCommandEvent& event)
{
  wxString ssScale = wxGetTextFromUser(
      _("Multiply scale of selected objects"), _("Scale factor:"), _("1.0"), this);
  std::string sScale = std::string(ssScale.mb_str());
  const float s = atof(sScale.c_str());

  for (auto& m_selected_gl_object : m_selected_gl_objects)
    if (m_selected_gl_object)
    {
      const float sx = m_selected_gl_object->getScaleX();
      const float sy = m_selected_gl_object->getScaleY();
      const float sz = m_selected_gl_object->getScaleZ();
      m_selected_gl_object->setScale(s * sx, s * sy, s * sz);
    }
  Refresh(false);
}

// Import a LAS LiDAR file:
void _DSceneViewerFrame::OnmnuImportLASSelected(wxCommandEvent& event)
{
  try
  {
#if MRPT_HAS_LIBLAS
    wxFileDialog dialog(
        this, _("Choose the LAS file to import"),
        (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), _("*.las"),
        _("LAS files (*.las, "
          "*.laz)|*.las;*.LAS;*.laz;*.LAZ|All files (*.*)|*.*"),
        wxFD_OPEN | wxFD_FILE_MUST_EXIST);

    if (dialog.ShowModal() != wxID_OK) return;

    const std::string fil = string(dialog.GetPath().mb_str());
    saveLastUsedDirectoryToCfgFile(fil);

    CDlgPLYOptions dlgPLY(this);

    // Default values for LAS:
    dlgPLY.SetTitle(_("LAS import options"));
    dlgPLY.edRoll->SetValue(_("0"));
    dlgPLY.rbIntFromXYZ->SetSelection(3);

    if (dlgPLY.ShowModal() != wxID_OK) return;

    opengl::CPointCloud::Ptr gl_points;
    opengl::CPointCloudColoured::Ptr gl_points_col;

    if (dlgPLY.rbClass->GetSelection() == 0)
      gl_points = std::make_shared<opengl::CPointCloud>();
    else
      gl_points_col = std::make_shared<opengl::CPointCloudColoured>();

    mrpt::maps::CColouredPointsMap pts_map;
    mrpt::maps::LAS_HeaderInfo las_hdr;

    bool res;
    {
      wxBusyCursor busy;
      res = mrpt::maps::loadLASFile(pts_map, fil, las_hdr);
    }

    if (!res)
    {
      wxMessageBox(_("Error loading or parsing the LAS file"), _("Exception"), wxOK, this);
      return;
    }

    mrpt::math::TBoundingBox bb;
    {
      wxBusyCursor busy;
      if (gl_points_col)
      {
        gl_points_col->loadFromPointsMap(&pts_map);
        bb = gl_points_col->getBoundingBox();
      }
      else
      {
        gl_points->loadFromPointsMap(&pts_map);
        bb = gl_points->getBoundingBox();
      }
    }

    const double scene_size = bb.min.distanceTo(bb.max);

    // Set the point cloud as the only object in scene:
    auto scene = std::make_shared<opengl::Scene>();
    m_canvas->setOpenGLSceneRef(scene);

    if (dlgPLY.cbXYGrid->GetValue())
    {
      mrpt::opengl::CGridPlaneXY::Ptr obj = mrpt::opengl::CGridPlaneXY::Create(
          bb.min.x, bb.max.x, bb.min.y, bb.max.y, 0, scene_size * 0.02);
      obj->setColor(0.3f, 0.3f, 0.3f);
      scene->insert(obj);
    }

    if (dlgPLY.cbXYZ->GetValue()) scene->insert(mrpt::opengl::stock_objects::CornerXYZ());

    double ptSize;
    dlgPLY.cbPointSize->GetStringSelection().ToCDouble(&ptSize);
    if (gl_points) gl_points->setPointSize(ptSize);
    if (gl_points_col) gl_points_col->setPointSize(ptSize);

    if (gl_points)
    {
      switch (dlgPLY.rbIntFromXYZ->GetSelection())
      {
        case 1:
          gl_points->enableColorFromX();
          break;
        case 2:
          gl_points->enableColorFromY();
          break;
        case 3:
          gl_points->enableColorFromZ();
          break;
      };
    }

    TPose3D ptCloudPose(0, 0, 0, 0, 0, 0);

    dlgPLY.edYaw->GetValue().ToCDouble(&ptCloudPose.yaw);
    dlgPLY.edPitch->GetValue().ToCDouble(&ptCloudPose.pitch);
    dlgPLY.edRoll->GetValue().ToCDouble(&ptCloudPose.roll);
    ptCloudPose.yaw = DEG2RAD(ptCloudPose.yaw);
    ptCloudPose.pitch = DEG2RAD(ptCloudPose.pitch);
    ptCloudPose.roll = DEG2RAD(ptCloudPose.roll);

    if (gl_points) gl_points->setPose(CPose3D(ptCloudPose));
    if (gl_points_col) gl_points_col->setPose(CPose3D(ptCloudPose));

    // Insert point cloud into scene:
    if (gl_points) scene->insert(gl_points);
    if (gl_points_col) scene->insert(gl_points_col);

    m_canvas->setCameraPointing(
        (bb.min.x + bb.max.x) * 0.5, (bb.min.y + bb.max.y) * 0.5, (bb.min.z + bb.max.z) * 0.5);

    m_canvas->setZoomDistance(2 * scene_size);
    m_canvas->setAzimuthDegrees(45);
    m_canvas->setElevationDegrees(30);

    Viewport::Ptr gl_view = scene->getViewport();
    gl_view->setViewportClipDistances(0.01, 10 * scene_size);

    loadedFileName = std::string("Imported_") + fil + std::string(".3Dscene");
    updateTitle();

    // Typically, LAS scenes have millions of points:
    mrpt::global_settings::OCTREE_RENDER_MAX_DENSITY_POINTS_PER_SQPIXEL(1000);

    Refresh(false);

    std::stringstream ss;
    ss << pts_map.size() << " points loaded.\n"
       << "Bounding box:\n"
       << " X: " << bb.min.x << " <=> " << bb.max.x << "\n"
       << " Y: " << bb.min.y << " <=> " << bb.max.y << "\n"
       << " Z: " << bb.min.z << " <=> " << bb.max.z << "\n"
       << "LAS header info:\n"
       << "---------------------------------\n"
       << "FileSignature      : " << las_hdr.FileSignature << endl
       << "SystemIdentifier   : " << las_hdr.SystemIdentifier << endl
       << "SoftwareIdentifier : " << las_hdr.SoftwareIdentifier << endl
       << "project GUID       : " << las_hdr.project_guid << endl
       << "SRS Proj.4         : " << las_hdr.spatial_reference_proj4 << endl
       << "Creation date      : Year=" << las_hdr.creation_year << " DOY=" << las_hdr.creation_DOY
       << endl;

    wxMessageBox(ss.str().c_str(), _("File info"), wxOK, this);
#else
    throw std::runtime_error("MRPT was built without libLAS support!");
#endif
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}

void _DSceneViewerFrame::OnmnuImportImageView(wxCommandEvent&)
{
  try
  {
    auto scene = std::make_shared<opengl::Scene>();
    m_canvas->setOpenGLSceneRef(scene);
    wxFileDialog dialog(
        this, _("Choose the LAS file to import"),
        (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), _("*.png"),
        _("Image files (*.png, "
          "*.jpg, *.bmp)|*.png;*.jpg;*.jpeg;*.bmp|All files (*.*)|*.*"),
        wxFD_OPEN | wxFD_FILE_MUST_EXIST);

    if (dialog.ShowModal() != wxID_OK) return;

    const std::string fil = string(dialog.GetPath().mb_str());
    saveLastUsedDirectoryToCfgFile(fil);

    mrpt::img::CImage im;
    if (!im.loadFromFile(fil)) THROW_EXCEPTION_FMT("Error loading image file: '%s'", fil.c_str());

    Viewport::Ptr gl_view = scene->getViewport();
    gl_view->setImageView(im);

    Refresh();
  }
  catch (const std::exception& e)
  {
    wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
  }
}
