/* +------------------------------------------------------------------------+
   |                     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 <mrpt/gui/CDisplayWindow3D.h>
#include <mrpt/maps/COctoMap.h>
#include <mrpt/obs/stock_observations.h>
#include <mrpt/opengl.h>
#include <mrpt/opengl/CPlanarLaserScan.h>
#include <mrpt/random.h>
#include <mrpt/system/filesystem.h>

#include <Eigen/Dense>  // transpose()
#include <chrono>
#include <iostream>
#include <thread>

using namespace std;
using namespace mrpt::literals;  // _deg
using namespace mrpt;            // opengl::
using namespace mrpt::gui;
using namespace mrpt::opengl;
using namespace mrpt::math;
using namespace std::string_literals;

// ------------------------------------------------------
//				TestOpenGLObjects
// ------------------------------------------------------
void TestOpenGLObjects()
{
  mrpt::global_settings::OCTREE_RENDER_MAX_POINTS_PER_NODE(10000);

  CDisplayWindow3D win("Demo of MRPT's OpenGL objects", 640, 480);

  Scene::Ptr& theScene = win.get3DSceneAndLock();

  auto& rng = mrpt::random::getRandomGenerator();

  // Objects:
  double off_x = 0;
  const double off_y_label = 20;
  const double STEP_X = 25;

  // XY Grid
  /**
   * Define each one of the objects in its own scope and just attach it to the
   * scene by using insert(obj) method call.
   */
  {
    // using mrpt smart pointers so that obj survives outside this scope.
    auto obj = opengl::CGridPlaneXY::Create(-7, 7, -7, 7, 0, 1);
    obj->setColor(0.7f, 0.7f, 0.7f);
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    auto obj2 = opengl::CGridPlaneXY::Create(-9, 9, -9, 9, 0, 2);
    obj2->setColor(0.3f, 0.3f, 0.3f, 0.99f);
    obj2->setLocation(off_x, 20, 0);
    obj2->enableAntiAliasing();
    theScene->insert(obj2);

    auto gl_txt = opengl::CText::Create("CGridPlaneXY");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // XZ Grid
  {
    auto obj = opengl::CGridPlaneXZ::Create(-7, 7, -7, 7, 0, 1);
    obj->setColor(0.7f, 0.7f, 0.7f);
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    auto gl_txt = opengl::CText::Create("CGridPlaneXZ");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // Arrow
  {
    auto obj = opengl::CArrow::Create(0, 0, 0, 3, 0, 0, 0.2f, 0.1f, 0.2f);
    obj->setLocation(off_x, 0, 0);
    obj->setColor(1, 0, 0);
    obj->setName("arrow #1");
    obj->enableShowName();
    theScene->insert(obj);

    auto obj2 = opengl::CArrow::Create(1, 2, 3, 6, -3, 0, 0.1f, 0.1f, 0.3f);
    obj2->setLocation(off_x, 0, 0);
    obj2->setColor(0, 0, 1);
    theScene->insert(obj2);

    auto gl_txt = opengl::CText::Create("CArrow");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // Axis
  {
    auto obj = opengl::CAxis::Create(-6, -6, -6, 6, 6, 6, 2, 2, true);
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    auto gl_txt = opengl::CText::Create("CAxis");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // Box
  {
    auto obj = opengl::CBox::Create(TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), true, 3.0);
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    auto obj2 = opengl::CBox::Create(TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), false);
    obj2->setLocation(off_x, 4, 0);
    theScene->insert(obj2);

    auto obj3 = opengl::CBox::Create(TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), false);
    obj3->enableBoxBorder(true);
    obj3->setLineWidth(3);
    obj3->setColor_u8(0xff, 0x00, 0x00, 0xa0);
    obj3->setLocation(off_x, 8, 0);
    theScene->insert(obj3);

    auto gl_txt = opengl::CText::Create("CBox");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // Frustum
  {
    auto obj = opengl::CFrustum::Create(1, 5, 60, 45, 1.5f, true, false);
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    auto obj2 = opengl::CFrustum::Create(1, 5, 60, 45, 2.5f, true, true);
    obj2->setLocation(off_x, 6, 0);
    obj2->setColor_u8(0xff, 0x00, 0x00, 0x80);
    theScene->insert(obj2);

    // Default camera params:
    mrpt::img::TCamera c;
    c.nrows = 800;
    c.ncols = 600;
    c.setIntrinsicParamsFromValues(700, 700, 400, 300);

    auto obj3 = opengl::CFrustum::Create(c);
    obj3->setLocation(off_x, 12, 0);
    obj3->setColor_u8(0xff, 0x00, 0x00, 0x80);
    theScene->insert(obj3);

    auto gl_txt = opengl::CText::Create("CFrustum");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // Cylinder
  {
    auto obj = opengl::CCylinder::Create(2, 2, 4, 20);
    obj->setLocation(off_x, 0, 0);
    obj->setColor(0, 0, 0.8f);
    theScene->insert(obj);

    auto obj2 = opengl::CCylinder::Create(2, 1, 4, 20);
    obj2->setLocation(off_x, 6, 0);
    obj2->setColor(0, 0, 0.8f);
    theScene->insert(obj2);

    auto obj3 = opengl::CCylinder::Create(2, 0, 4, 20);
    obj3->setLocation(off_x, -6, 0);
    obj3->setColor(0, 0, 0.8f);
    theScene->insert(obj3);

    auto gl_txt = opengl::CText::Create("CCylinder");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CDisk
  {
    {
      auto obj = opengl::CDisk::Create(2.0f, 1.8f, 50);
      obj->setLocation(off_x, 0, 0);
      obj->setColor(0.8f, 0, 0);
      theScene->insert(obj);
    }

    {
      auto obj = opengl::CDisk::Create(2.0f, 0, 50);
      obj->setLocation(off_x, 5, 0);
      obj->setColor(0.8f, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CDisk");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CEllipsoid3D
  {
    const double cov3d_dat[] = {0.9, 0.7, -0.4, 0.7, 1.6, -0.6, -0.4, -0.6, 1.5};
    const double cov2d_dat[] = {0.9, 0.7, 0.7, 1.6};
    mrpt::math::CMatrixDouble22 cov2d(cov2d_dat);
    mrpt::math::CMatrixDouble33 cov3d(cov3d_dat);

    {
      auto obj = opengl::CEllipsoid2D::Create();
      obj->setCovMatrix(cov2d);
      obj->setLocation(off_x, 6, 0);
      obj->setQuantiles(2.0);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CEllipsoid2D::Create();
      obj->setCovMatrix(cov2d);
      obj->setLocation(off_x, 12, 0);
      obj->enableDrawSolid3D(true);
      obj->setQuantiles(2.0);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CEllipsoid3D::Create();
      obj->setCovMatrix(cov3d);
      obj->setQuantiles(2.0);
      obj->enableDrawSolid3D(false);
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CEllipsoid3D::Create();
      obj->setCovMatrix(cov3d);
      obj->setQuantiles(2.0);
      obj->materialShininess(0.99f);
      obj->setName("Ellipsoid shininess=0.99");
      obj->enableShowName();
      obj->enableDrawSolid3D(true);
      obj->setLocation(off_x, -6, 0);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CEllipsoid3D::Create();
      obj->setCovMatrix(cov3d);
      obj->setQuantiles(2.0);
      obj->materialShininess(0.01f);
      obj->setName("Ellipsoid shininess=0.01");
      obj->enableShowName();
      obj->enableDrawSolid3D(true);
      obj->setLocation(off_x, -12, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CEllipsoid3D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CEllipsoidRangeBearing2D
  {  // (range,bearing) -> (x,y)
    const double cov_params_dat[] = {0.2, 0, 0, 0.1};
    const double mean_params_dat[] = {3.0, 0.5};
    mrpt::math::CMatrixFixed<double, 2, 2> cov_params(cov_params_dat);
    mrpt::math::CMatrixFixed<double, 2, 1> mean_params(mean_params_dat);

    {
      auto obj = opengl::CEllipsoidRangeBearing2D::Create();
      obj->setCovMatrixAndMean(cov_params, mean_params);
      obj->setLocation(off_x, 6, 0);
      obj->setQuantiles(2.0f);
      // obj->setNumberOfSegments(50);
      theScene->insert(obj);

      auto obj_corner = opengl::stock_objects::CornerXYSimple(1, 3);
      obj_corner->setLocation(off_x, 6, 0);
      theScene->insert(obj_corner);
    }
  }
  {  // (range,bearing) -> (x,y)
    const double cov_params_dat[] = {0.2, 0.09, 0.09, 0.1};
    const double mean_params_dat[] = {5.0, -0.5};
    mrpt::math::CMatrixFixed<double, 2, 2> cov_params(cov_params_dat);
    mrpt::math::CMatrixFixed<double, 2, 1> mean_params(mean_params_dat);

    {
      auto obj = opengl::CEllipsoidRangeBearing2D::Create();
      obj->setCovMatrixAndMean(cov_params, mean_params);
      obj->setLocation(off_x, 0, 0);
      obj->setQuantiles(2.0f);
      // obj->setNumberOfSegments(50);
      theScene->insert(obj);

      auto obj_corner = opengl::stock_objects::CornerXYSimple(1, 3);
      obj_corner->setLocation(off_x, 0, 0);
      theScene->insert(obj_corner);
    }

    auto gl_txt = opengl::CText::Create("CEllipsoidRangeBearing2D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CEllipsoidInverseDepth2D
  {  // (inv_range,yaw) -> (x,y)
    // Formula from our book (ch8) for confidence intervals of 3sigmas:
    const double max_dist = 1e4;
    const double min_dist = 1;
    const double rho_mean = 0.5 * (1. / min_dist + 1. / max_dist);
    const double rho_std = (1. / 6.) * (1. / min_dist - 1. / max_dist);

    const double cov_params_dat[] = {square(rho_std), 0, 0, square(2.0_deg)};
    const double mean_params_dat[] = {rho_mean, 70.0_deg};
    mrpt::math::CMatrixFixed<double, 2, 2> cov_params(cov_params_dat);
    mrpt::math::CMatrixFixed<double, 2, 1> mean_params(mean_params_dat);

    {
      auto obj = opengl::CEllipsoidInverseDepth2D::Create();
      obj->setCovMatrixAndMean(cov_params, mean_params);
      obj->setLocation(off_x, 6, 0);
      obj->setQuantiles(3.f);
      obj->setNumberOfSegments(100);
      theScene->insert(obj);

      auto obj_corner = opengl::stock_objects::CornerXYSimple(1, 3);
      obj_corner->setLocation(off_x, 6, 0);
      theScene->insert(obj_corner);
    }

    auto gl_txt = opengl::CText::Create("CEllipsoidInverseDepth2D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CEllipsoidInverseDepth3D
  {  // (inv_range,yaw,pitch) -> (x,y,z)
    // Formula from our book (ch8) for confidence intervals of 3sigmas:
    const double max_dist = 1e2;
    const double min_dist = 1;
    const double rho_mean = 0.5 * (1. / min_dist + 1. / max_dist);
    const double rho_std = (1. / 6.) * (1. / min_dist - 1. / max_dist);

    const double cov_params_dat[] = {square(rho_std), 0, 0, 0, square(2.0_deg), 0, 0, 0,
                                     square(2.0_deg)};
    const double mean_params_dat[] = {rho_mean, 30.0_deg, -45.0_deg};
    mrpt::math::CMatrixFixed<double, 3, 3> cov_params(cov_params_dat);
    mrpt::math::CMatrixFixed<double, 3, 1> mean_params(mean_params_dat);

    {
      auto obj = opengl::CEllipsoidInverseDepth3D::Create();
      obj->setCovMatrixAndMean(cov_params, mean_params);
      obj->setLocation(off_x, 0, 0);
      obj->setQuantiles(3.f);
      // obj->setNumberOfSegments(50);
      theScene->insert(obj);

      auto obj_corner = opengl::stock_objects::CornerXYZSimple(1, 3);
      obj_corner->setLocation(off_x, 0, 0);
      theScene->insert(obj_corner);
    }

    auto gl_txt = opengl::CText::Create("CEllipsoidInverseDepth3D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CMesh3D
  {
    auto obj = opengl::CMesh3D::Create();
    obj->enableShowEdges(false);
    obj->enableShowFaces(true);
    obj->enableShowVertices(false);
    obj->setLocation(off_x, 0, 0);

    const unsigned int rows = 200, cols = 200;
    const unsigned int num_verts = (rows + 1) * (cols + 1);
    const unsigned int num_faces = rows * cols;
    int* vert_per_face = new int[num_faces];
    int* face_verts = new int[4 * num_faces];
    float* vert_coords = new float[3 * num_verts];

    // Assign vertices to faces and set them to be Quads
    unsigned int first_ind = 0;
    for (unsigned int u = 0; u < cols; u++)
    {
      for (unsigned int v = 0; v < rows; v++)
      {
        const unsigned int face_ind = v + u * rows;
        vert_per_face[face_ind] = 4;

        face_verts[4 * face_ind] = first_ind;
        face_verts[4 * face_ind + 1] = first_ind + 1;
        face_verts[4 * face_ind + 2] = first_ind + rows + 2;
        face_verts[4 * face_ind + 3] = first_ind + rows + 1;
        first_ind++;
      }

      first_ind++;
    }

    // Create vert coords
    for (unsigned int u = 0; u <= cols; u++)
      for (unsigned int v = 0; v <= rows; v++)
      {
        const unsigned int vert_ind = v + u * (rows + 1);

        vert_coords[3 * vert_ind] =
            (2.f - 0.01f * u) * (2.f + cos(0.01f * M_PI * v)) * cos(0.01f * M_PI * u);
        vert_coords[3 * vert_ind + 1] =
            (2.f - 0.01f * u) * (2.f + cos(0.01f * M_PI * v)) * sin(0.01f * M_PI * u);
        vert_coords[3 * vert_ind + 2] = 3.f * 0.01f * u + (2.f - 0.01f * u) * sin(0.01f * M_PI * v);
      }

    obj->loadMesh(num_verts, num_faces, vert_per_face, face_verts, vert_coords);
    theScene->insert(obj);

    auto gl_txt = opengl::CText::Create("CMesh3D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CMeshFast, CMesh:
  {
    opengl::CMeshFast::Ptr obj1 = opengl::CMeshFast::Create();
    opengl::CMeshFast::Ptr obj2 = opengl::CMeshFast::Create();
    opengl::CMesh::Ptr obj3 = opengl::CMesh::Create();
    opengl::CMesh::Ptr obj4 = opengl::CMesh::Create();
    opengl::CMesh::Ptr obj5 = opengl::CMesh::Create();

    obj1->setXBounds(-1, 1);
    obj1->setYBounds(-1, 1);

    const int W = 128, H = 128;

    mrpt::math::CMatrixDynamic<float> Z(H, W);

    for (int r = 0; r < H; r++)
      for (int c = 0; c < W; c++) Z(r, c) = sin(0.05 * (c + r) - 0.5) * cos(0.9 - 0.03 * r);

    const std::string texture_file =
        mrpt::system::getShareMRPTDir() + "datasets/sample-texture-terrain.jpg"s;

    const std::string texture_file2 =
        mrpt::system::getShareMRPTDir() + "datasets/sample-texture-corners.jpg"s;

    mrpt::img::CImage im, im2;

    // obj1:
    obj1->setZ(Z);
    obj1->enableColorFromZ(true);
    obj1->setPointSize(2.0);
    obj1->setLocation(off_x, 0, 0);
    theScene->insert(obj1);

    // obj 2:
    if (im.loadFromFile(texture_file))
    {
      obj2->assignImageAndZ(im, Z);
      obj2->setPointSize(2.0);
      obj2->setLocation(off_x, 3, 0);
      theScene->insert(obj2);
    }
    {
      auto gl_txt = opengl::CText::Create("CMeshFast");
      gl_txt->setLocation(off_x, off_y_label, 0);
      theScene->insert(gl_txt);
    }

    off_x += STEP_X;

    // obj 3:
    obj3->setZ(Z);
    obj3->enableColorFromZ(true, mrpt::img::cmJET);
    obj3->enableWireFrame(true);
    obj3->setLocation(off_x, 0, 0);
    obj3->cullFaces(mrpt::opengl::TCullFace::BACK);
    // obj3->enableTextureMipMap(false);
    theScene->insert(obj3);

    // obj 4:
    if (im.getWidth() > 1)
    {
      obj4->assignImageAndZ(im, Z);
      obj4->setLocation(off_x, 3, 0);
      obj4->cullFaces(mrpt::opengl::TCullFace::BACK);
      // obj4->enableTextureMipMap(false);
      theScene->insert(obj4);
    }
    // obj 5:
    if (im.getWidth() > 1)
    {
      obj5->assignImageAndZ(im, Z);
      obj5->setMeshTextureExtension(0.25, 0.5);
      obj5->setLocation(off_x + 3, 3, 0);
      // obj5->enableTextureMipMap(false);
      theScene->insert(obj5);
    }

    mrpt::math::CMatrixDynamic<float> Z2(H, W);

    if (im2.loadFromFile(texture_file2))
    {
      im2.getAsMatrix(Z2);

      opengl::CMesh::Ptr obj = opengl::CMesh::Create(
          false /*transparency*/,  //
          -1, 1,                   // x: Min Max
          1, -1                    // y: Min Max
      );
      obj->assignImageAndZ(im2, Z2);
      obj->setLocation(off_x, 6, 0);
      theScene->insert(obj);
    }
    if (im2.loadFromFile(texture_file2))
    {
      opengl::CMesh::Ptr obj = opengl::CMesh::Create(
          false /*transparency*/,  //
          1, -1,                   // x: Min Max
          -1, 1                    // y: Min Max
      );
      obj->assignImageAndZ(im2, Z2);
      obj->setLocation(off_x, 9, 0);
      theScene->insert(obj);
    }

    {
      auto gl_txt = opengl::CText::Create("CMesh");
      gl_txt->setLocation(off_x, off_y_label, 0);
      theScene->insert(gl_txt);
    }
  }
  off_x += STEP_X;

  // CPointCloud
  {
    auto obj = opengl::CPointCloud::Create();
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    obj->setPointSize(1.0);
    obj->enableColorFromY();

    for (int i = 0; i < 100000; i++)
      obj->insertPoint(
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(-5, 5),
          rng.drawUniform<float>(-5, 5));

    auto gl_txt = opengl::CText::Create("CPointCloud");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CPointCloudColoured
  {
    auto obj = opengl::CPointCloudColoured::Create();
    obj->setLocation(off_x, 0, 0);
    theScene->insert(obj);

    obj->setPointSize(1.0);

    for (int i = 0; i < 200; i++)
      obj->push_back(
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(-5, 5),
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(0, 1), rng.drawUniform<float>(0, 1),
          rng.drawUniform<float>(0, 1));

    auto gl_txt = opengl::CText::Create("CPointCloudColoured");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CPolyhedron
  {
    {
      auto obj = opengl::CPolyhedron::CreateCuboctahedron(1.0);
      obj->setLocation(off_x, 0, 0);
      obj->setWireframe(true);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CPolyhedron::CreateDodecahedron(1.0);
      obj->setLocation(off_x, -5, 0);
      theScene->insert(obj);
    }
    {
      auto obj = opengl::CPolyhedron::CreateIcosahedron(1.0);
      obj->setLocation(off_x, 5, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CPolyhedron");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CSphere
  {
    {
      auto obj = opengl::CSphere::Create(3.0);
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CSphere");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CText
  {
    {
      auto obj = opengl::CText::Create(
          "This is a CText example! My size is invariant to "
          "eye-distance");
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CText");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CText3D
  {
    {
      auto obj = opengl::CText3D::Create("I'm a cool CText3D!");
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    {
      auto obj = opengl::CText3D::Create("A rotated CText3D");
      obj->setPose(
          mrpt::poses::CPose3D(off_x, 0, 0, 0, 0, 0) +
          mrpt::poses::CPose3D::FromString("[0 5 0 180 0 90]"));
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CText3D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CColorMap
  {
    {
      auto obj = opengl::CColorBar::Create(
          mrpt::img::cmHOT, 0.2, 1.0, 0.0, 1.0, -50.0, 100.0, "%7.02f m/s");
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CColorBar");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CSetOfLines
  {
    for (int rep = 0; rep < 2; rep++)
    {
      auto obj = opengl::CSetOfLines::Create();
      obj->setLocation(off_x, rep * 20, 0);

      for (int i = 0; i < 15; i++)
        obj->appendLine(
            rng.drawUniform(-5, 5), rng.drawUniform(-5, 5), rng.drawUniform(-5, 5),
            rng.drawUniform(-5, 5), rng.drawUniform(-5, 5), rng.drawUniform(-5, 5));

      if (rep == 1) obj->setVerticesPointSize(5.0f);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CSetOfLines");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CSimpleLine
  {
    {
      auto obj = opengl::CSimpleLine::Create();
      obj->setLocation(off_x, 0, 0);

      obj->setLineCoords(
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(-5, 5),
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(-5, 5),
          rng.drawUniform<float>(-5, 5), rng.drawUniform<float>(-5, 5));

      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CSimpleLine");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CVectorField2D
  {
    {
      auto obj = opengl::CVectorField2D::Create();
      obj->setLocation(off_x, 0, 0);

      CMatrixFloat x(16, 16), y(16, 16);
      for (int i = 0; i < x.rows(); i++)
        for (int j = 0; j < x.cols(); j++)
        {
          x(i, j) = sin(0.3 * i);
          y(i, j) = cos(0.3 * i);
        }
      obj->setVectorField(x, y);
      obj->setPointColor(1, 0.3f, 0);
      obj->setVectorFieldColor(0, 0, 1);
      obj->setPointSize(3.0);
      obj->setLineWidth(2.0);
      obj->setGridCenterAndCellSize(0, 0, 1.2f, 1.2f);
      obj->adjustVectorFieldToGrid();
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CVectorField2D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CVectorField3D
  {
    {
      const unsigned int num = 20;
      const float scale = 0.8 * STEP_X / num;
      auto obj = opengl::CVectorField3D::Create();
      obj->setLocation(off_x, -0.5 * scale * num, 0);  //

      CMatrixFloat x(num, num), y(num, num), z(num, num);
      CMatrixFloat vx(num, num), vy(num, num), vz(num, num);
      for (int i = 0; i < x.rows(); i++)
        for (int j = 0; j < x.cols(); j++)
        {
          x(i, j) = (i - 0.5 * num) * scale;
          y(i, j) = j * scale;
          z(i, j) = 3 * sin(0.3 * i) * cos(0.3 * j);
          vx(i, j) = 0.4 * sin(0.3 * i);
          vy(i, j) = 0.8 * cos(0.3 * i);
          vz(i, j) = 0.01 * i * j;
        }
      obj->setPointCoordinates(x, y, z);
      obj->setVectorField(vx, vy, vz);
      obj->setPointColor(1, 0.3f, 0);
      obj->setVectorFieldColor(0, 0, 1);
      obj->setPointSize(3.0);
      obj->setLineWidth(2.0);
      obj->enableColorFromModule();
      obj->setMaxSpeedForColor(3.0);
      obj->setMotionFieldColormap(0, 0, 1, 1, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CVectorField3D");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::BumblebeeCamera
  {
    {
      auto obj = opengl::stock_objects::BumblebeeCamera();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::BumblebeeCamera()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::CornerXYSimple
  {
    {
      auto obj = opengl::stock_objects::CornerXYSimple(1, 3);
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::CornerXYSimple()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::CornerXYZSimple
  {
    {
      auto obj = opengl::stock_objects::CornerXYZSimple(1, 3);
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::CornerXYZSimple()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::CornerXYZ
  {
    {
      auto obj = opengl::stock_objects::CornerXYZ();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::CornerXYZ()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CSetOfTriangles: tested via stock_objects:
  // stock_objects::RobotPioneer
  {
    {
      auto obj = opengl::stock_objects::RobotPioneer();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::RobotPioneer()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::Hokuyo_URG
  {
    {
      auto obj = opengl::stock_objects::Hokuyo_URG();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::Hokuyo_URG()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::Hokuyo_UTM
  {
    {
      auto obj = opengl::stock_objects::Hokuyo_UTM();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::Hokuyo_UTM()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::Househam_Sprayer
  {
    {
      auto obj = opengl::stock_objects::Househam_Sprayer();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::Househam_Sprayer()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // stock_objects::RobotRhodon
  {
    {
      auto obj = opengl::stock_objects::RobotRhodon();
      obj->setLocation(off_x, 0, 0);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("stock_objects::RobotRhodon()");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CPlanarLaserScan
  {
    auto obj = mrpt::opengl::CPlanarLaserScan::Create();
    obj->setPose(mrpt::poses::CPose3D(off_x, 0, 0, 90.0_deg, 0, 0));

    mrpt::obs::CObservation2DRangeScan scan;
    mrpt::obs::stock_observations::example2DRangeScan(scan);

    obj->setScan(scan);

    theScene->insert(obj);

    auto gl_txt = opengl::CText::Create("CPlanarLaserScan");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // CTexturedPlane
  {
    mrpt::img::CImage pic, picAlpha;
    const size_t W = 256, H = 256;
    pic.resize(W, H, mrpt::img::CH_RGB);
    picAlpha.resize(W, H, mrpt::img::CH_GRAY);
    pic.filledRectangle(0, 0, W - 1, H - 1, mrpt::img::TColor::black());
    pic.filledRectangle(0, 0, W / 4, H / 2, mrpt::img::TColor::white());

    picAlpha.filledRectangle(0, 0, W - 1, H - 1, mrpt::img::TColor(0x55, 0x55, 0x55));
    picAlpha.filledRectangle(0, 0, W / 4, H / 2, mrpt::img::TColor(0xa0, 0xa0, 0xa0));

    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 0, 0, 0, 90.0_deg, 0));
      obj->assignImage(pic);
      theScene->insert(obj);
    }

    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 4.0, 0, 0, 90.0_deg, 0));
      obj->assignImage(pic, picAlpha);
      theScene->insert(obj);
    }

    // a plane w/o a texture is a plain color plane:
    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 8.0, 0, 0, 90.0_deg, 0));
      obj->setColor_u8(0xff, 0x00, 0x00, 0xff);
      theScene->insert(obj);
    }
    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 12.0, 0, 0, 90.0_deg, 0));
      obj->setColor_u8(0xff, 0x00, 0x00, 0x40);
      theScene->insert(obj);
    }

    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 6.0, -3.0, 0, 0.0_deg, 0));
      obj->assignImage(pic);
      obj->enableLighting(true);
      theScene->insert(obj);
    }
    {
      opengl::CTexturedPlane::Ptr obj = opengl::CTexturedPlane::Create();
      obj->setPose(mrpt::poses::CPose3D(off_x, 3.9, -3.0, 0, 0.0_deg, 0));
      obj->setColor_u8(0xff, 0x00, 0x00, 0xff);
      obj->enableLighting(true);
      theScene->insert(obj);
    }

    auto gl_txt = opengl::CText::Create("CTexturedPlane");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // COctoMapVoxels
  {
    mrpt::maps::COctoMap map(0.2);
    // Insert 2D scan:

    mrpt::obs::CObservation2DRangeScan scan1;
    mrpt::obs::stock_observations::example2DRangeScan(scan1);
    map.insertObservation(scan1);

    auto gl_map1 = mrpt::opengl::COctoMapVoxels::Create();
    auto gl_map2 = mrpt::opengl::COctoMapVoxels::Create();
    auto gl_map3 = mrpt::opengl::COctoMapVoxels::Create();

    map.getAsOctoMapVoxels(*gl_map1);

    map.getAsOctoMapVoxels(*gl_map2);

    // map.renderingOptions.generateGridLines = true;
    map.getAsOctoMapVoxels(*gl_map3);

    gl_map1->showGridLines(false);
    gl_map1->showVoxelsAsPoints(true);
    gl_map1->setPointSize(3.0);
    gl_map1->setLocation(off_x, 0, 0);
    theScene->insert(gl_map1);

    gl_map2->showVoxelsAsPoints(false);
    gl_map2->showVoxels(VOXEL_SET_OCCUPIED, true);
    gl_map2->showVoxels(VOXEL_SET_FREESPACE, true);
    gl_map2->setLocation(off_x, 4, 0);
    theScene->insert(gl_map2);

    gl_map3->showVoxelsAsPoints(true);
    gl_map3->showGridLines(true);
    gl_map3->setLocation(off_x, 8, 0);
    theScene->insert(gl_map3);

    auto gl_txt = opengl::CText::Create("COctoMapVoxels");
    gl_txt->setLocation(off_x, off_y_label, 0);
    theScene->insert(gl_txt);
  }
  off_x += STEP_X;

  // ground plane (to test shadows):
  // A plane w/o a texture is a plain color plane:
  {
    auto obj = mrpt::opengl::CBox::Create();
    obj->setColor_u8(0xa0, 0xa0, 0xa0, 0xff);
    obj->setLocation(0, 0, -5.0f);
    obj->setBoxCorners({-20.0f, -20.0f, .0f}, {off_x + 20.f, 40.0f, -0.1f});
    obj->cullFaces(TCullFace::BACK);  // avoid z-fighting
    theScene->insert(obj);
  }

  // Arrow to show the light direction:
  auto glLightArrow =
      opengl::CArrow::Create(mrpt::math::TPoint3Df(0, 0, 0), mrpt::math::TPoint3Df(1, 0, 0));
  glLightArrow->setLocation(off_x / 5, -4.0, 5.0);
  glLightArrow->setColor(0, 1, 0);
  glLightArrow->setName("Light dir.");
  glLightArrow->enableShowName();
  theScene->insert(glLightArrow);

  // Add image-mode viewport:
  {
    const std::string img_file =
        mrpt::system::getShareMRPTDir() + "datasets/stereo-calib/0_left.jpg"s;

    mrpt::img::CImage im;
    if (im.loadFromFile(img_file))
    {
      auto glView = theScene->createViewport("image1");
      glView->setViewportPosition(0.7, 0, 0.3, 0.3);
      glView->setImageView(im);

      glView->setBorderSize(1);
    }
  }

  // Zoom out:
  win.setCameraZoom(250);

  // IMPORTANT!!! IF NOT UNLOCKED, THE WINDOW WILL NOT BE UPDATED!
  win.unlockAccess3DScene();
  win.repaint();

  cout << "Close the window to end.\n";

  mrpt::opengl::TFontParams fp;
  fp.vfont_scale = 14;  // pixels
  fp.draw_shadow = true;
  win.addTextMessage(5, 5, "", 0 /*id*/, fp);

  auto viewport = theScene->getViewport();

  mrpt::opengl::TLightParameters& lights = viewport->lightParameters();

  lights.ambient = 0.2;

  while (win.isOpen())
  {
    // Lights:
    const double t = mrpt::Clock::nowDouble();
    const auto p = glLightArrow->getPose();

    const auto lightDir =
        mrpt::poses::CPose3D::FromXYZYawPitchRoll(p.x, p.y, p.z, t * 10.0_deg, 45.0_deg, 0.0_deg);

    glLightArrow->setPose(lightDir);

    lights.direction = lightDir.getRotationMatrix().extractColumn<mrpt::math::TVector3Df>(0);

    if (win.keyHit())
    {
      switch (win.getPushedKey())
      {
        case 'S':
        case 's':
          // toggle shadows:
          viewport->enableShadowCasting(!viewport->isShadowCastingEnabled());
          break;
      };
    }

    win.updateTextMessage(
        0 /*id*/, format(
                      "Render time=%.03fms | Shadows: %s", 1e3 / win.getRenderingFPS(),
                      viewport->isShadowCastingEnabled() ? "On" : "Off"));
    std::this_thread::sleep_for(2ms);
    win.repaint();
  }
}

// ------------------------------------------------------
//						MAIN
// ------------------------------------------------------
int main()
{
  try
  {
    TestOpenGLObjects();

    return 0;
  }
  catch (const std::exception& e)
  {
    std::cerr << "MRPT error: " << mrpt::exception_to_str(e) << std::endl;
    return -1;
  }
}
