/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/math/geometry.h>
#include <mrpt/opengl/CRenderizableShaderTriangles.h>
#include <mrpt/opengl/CRenderizableShaderWireFrame.h>

namespace mrpt
{
namespace opengl
{
class CPolyhedron;

/**
 * This class represents arbitrary polyhedra. The class includes a set of
 * static methods to create common polyhedrons. The class includes many methods
 * to create standard polyhedra, not intended to be fast but to be simple. For
 * example, the dodecahedron is not created efficiently: first, an icosahedron
 * is created, and a duality operator is applied to it, which yields the
 * dodecahedron. This way, code is much smaller, although much slower. This is
 * not a big problem, since polyhedron creation does not usually take a
 * significant amount of time (they are created once and rendered many times).
 * Polyhedra information and models have been gotten from the Wikipedia,
 * https://wikipedia.org
 *
 * ![mrpt::opengl::CPolyhedron](preview_CPolyhedron.png)
 *
 * \sa opengl::Scene
 * \ingroup mrpt_opengl_grp
 */
class CPolyhedron : public CRenderizableShaderWireFrame, public CRenderizableShaderTriangles
{
  DEFINE_SERIALIZABLE(CPolyhedron, mrpt::opengl)
 public:
  /** @name Renderizable shader API virtual methods
   * @{ */
  void render(const RenderContext& rc) const override;
  void renderUpdateBuffers() const override;
  void freeOpenGLResources() override
  {
    CRenderizableShaderTriangles::freeOpenGLResources();
    CRenderizableShaderWireFrame::freeOpenGLResources();
  }

  virtual shader_list_t requiredShaders() const override
  {
    // May use up to two shaders (triangles and lines):
    return {DefaultShaderID::WIREFRAME, DefaultShaderID::TRIANGLES_LIGHT};
  }
  void onUpdateBuffers_Wireframe() override;
  void onUpdateBuffers_Triangles() override;
  /** @} */

  /**
   * Struct used to store a polyhedron edge. The struct consists only of two
   * vertex indices, used to access the polyhedron vertex list.
   */
  struct TPolyhedronEdge
  {
    /**
     * Vertices.
     */
    uint32_t v1 = 0, v2 = 0;
    /**
     * Default constructor. Initializes to garbage.
     */
    TPolyhedronEdge() = default;
    /**
     * Comparison against another edge. Simmetry is taken into account.
     */
    bool operator==(const TPolyhedronEdge& e) const
    {
      if (e.v1 == v1 && e.v2 == v2)
        return true;
      else
        return e.v1 == v2 && e.v2 == v1;
    }
    /**
     * Given a set of vertices, computes the length of the vertex.
     */
    double length(const std::vector<mrpt::math::TPoint3D>& vs) const;
    /**
     * Destructor.
     */
    ~TPolyhedronEdge() = default;
  };
  /**
   * Struct used to store a polyhedron face. Consists on a set of vertex
   * indices and a normal vector.
   */
  struct TPolyhedronFace
  {
    /** Vector of indices to the vertex list. */
    std::vector<uint32_t> vertices;
    /** Normal vector. */
    mrpt::math::TVector3D normal;
    /** Fast default constructor. Initializes to garbage. */
    TPolyhedronFace() : vertices() {}
    /** Destructor.  */
    ~TPolyhedronFace() = default;
    /** Given a set of vertices, computes the area of this face. */
    double area(const std::vector<mrpt::math::TPoint3D>& vertices) const;
    /** Given a set of vertices, get this face's center. */
    void getCenter(
        const std::vector<mrpt::math::TPoint3D>& vertices, mrpt::math::TPoint3D& p) const;
  };

 protected:
  /**
   * List of vertices presents in the polyhedron.
   */
  std::vector<mrpt::math::TPoint3D> m_Vertices;
  /**
   * List of polyhedron's edges.
   */
  std::vector<TPolyhedronEdge> m_Edges;
  /**
   * List of polyhedron's faces.
   */
  std::vector<TPolyhedronFace> m_Faces;
  /**
   * This flag determines whether the polyhedron will be displayed as a solid
   * object or as a set of edges.
   */
  bool m_Wireframe{false};
  /**
   * Mutable list of actual polygons, maintained for speed.
   */
  mutable std::vector<mrpt::math::TPolygonWithPlane> tempPolygons;
  /**
   * Whether the set of actual polygons is up to date or not.
   */
  mutable bool polygonsUpToDate{false};

 public:
  /** Evaluates the bounding box of this object (including possible children)
   * in the coordinate frame of the object parent. */
  mrpt::math::TBoundingBoxf internalBoundingBoxLocal() const override;

  // Static methods to create frequent polyhedra. More bizarre polyhedra are
  // intended to be added in a near future.

  /** @name Platonic solids.
    @{
   */
  /**
  * Creates a regular tetrahedron (see
  http://en.wikipedia.org/wiki/Tetrahedron). The tetrahedron is created as a
  triangular pyramid whose edges and vertices are transitive.
  * The tetrahedron is the dual to itself.
  <p align="center"><img src="Tetrahedron.gif"></p>
  * \sa
  CreatePyramid,CreateJohnsonSolidWithConstantBase,CreateTruncatedTetrahedron
  */
  static CPolyhedron::Ptr CreateTetrahedron(double radius);
  /**
  * Creates a regular cube, also called hexahedron (see
  http://en.wikipedia.org/wiki/Hexahedron). The hexahedron is created as a
  cubic prism which transitive edges. Another ways to create it include:
  <ul><li>Dual to an octahedron.</li><li>Parallelepiped with three
  orthogonal, equally-lengthed vectors.</li><li>Triangular trapezohedron
  with proper height.</li></ul>
  <p align="center"><img src="Hexahedron.gif"></p>
  * \sa
  CreateOctahedron,getDual,CreateParallelepiped,CreateTrapezohedron,CreateTruncatedHexahedron,CreateTruncatedOctahedron,CreateCuboctahedron,CreateRhombicuboctahedron
  */
  static CPolyhedron::Ptr CreateHexahedron(double radius);
  /**
  * Creates a regular octahedron (see
  http://en.wikipedia.org/wiki/Octahedron). The octahedron is created as a
  square bipyramid whit transitive edges and vertices. Another ways to
  create an octahedron are:
  <ul><li>Dual to an hexahedron</li><li>Triangular antiprism with transitive
  vertices.</li><li>Conveniently truncated tetrahedron.</li></ul>
  <p align="center"><img src="Octahedron.gif"></p>
  * \sa
  CreateHexahedron,getDual,CreateArchimedeanAntiprism,CreateTetrahedron,truncate,CreateTruncatedOctahedron,CreateTruncatedHexahedron,CreateCuboctahedron,CreateRhombicuboctahedron
  */
  static CPolyhedron::Ptr CreateOctahedron(double radius);
  /**
  * Creates a regular dodecahedron (see
  http://en.wikipedia.org/wiki/Dodecahedron). The dodecahedron is created as
  the dual to an icosahedron.
  <p align="center"><img src="Dodecahedron.gif"></p>
  * \sa
  CreateIcosahedron,getDual,CreateTruncatedDodecahedron,CreateTruncatedIcosahedron,CreateIcosidodecahedron,CreateRhombicosidodecahedron
  */
  static CPolyhedron::Ptr CreateDodecahedron(double radius);
  /**
  * Creates a regular icosahedron (see
  http://en.wikipedia.org/wiki/Icosahedron). The icosahedron is created as a
  gyroelongated pentagonal bipyramid with transitive edges, and it's the
  dual to a dodecahedron.
  <p align="center"><img src="Icosahedron.gif"></p>
  * \sa
  CreateJohnsonSolidWithConstantBase,CreateDodecahedron,getDual,CreateTruncatedIcosahedron,CreateTruncatedDodecahedron,CreateIcosidodecahedron,CreateRhombicosidodecahedron
  */
  static CPolyhedron::Ptr CreateIcosahedron(double radius);
  /** @}
   */

  /** @name Archimedean solids.
    @{
   */
  /**
  * Creates a truncated tetrahedron, consisting of four triangular faces and
  for hexagonal ones (see
  http://en.wikipedia.org/wiki/Truncated_tetrahedron). Its dual is the
  triakis tetrahedron.
  <p align="center"><img src="Truncatedtetrahedron.gif"></p>
  * \sa CreateTetrahedron,CreateTriakisTetrahedron
  */
  static CPolyhedron::Ptr CreateTruncatedTetrahedron(double radius);
  /**
  * Creates a cuboctahedron, consisting of six square faces and eight
  triangular ones (see http://en.wikipedia.org/wiki/Cuboctahedron). There
  are several ways to create a cuboctahedron:
  <ul><li>Hexahedron truncated to a certain extent.</li><li>Octahedron
  truncated to a certain extent.</li><li>Cantellated
  tetrahedron</li><li>Dual to a rhombic dodecahedron.</li></ul>
  <p align="center"><img src="Cuboctahedron.gif"></p>
  * \sa
  CreateHexahedron,CreateOctahedron,truncate,CreateTetrahedron,cantellate,CreateRhombicuboctahedron,CreateRhombicDodecahedron,
  */
  static CPolyhedron::Ptr CreateCuboctahedron(double radius);
  /**
  * Creates a truncated hexahedron, with six octogonal faces and eight
  triangular ones (see http://en.wikipedia.org/wiki/Truncated_hexahedron).
  The truncated octahedron is dual to the triakis octahedron.
  <p align="center"><img src="Truncatedhexahedron.gif"></p>
  * \sa CreateHexahedron,CreateTriakisOctahedron
  */
  static CPolyhedron::Ptr CreateTruncatedHexahedron(double radius);
  /**
  * Creates a truncated octahedron, with eight hexagons and eight squares
  (see http://en.wikipedia.org/wiki/Truncated_octahedron). It's the dual to
  the tetrakis hexahedron.
  <p align="center"><img src="Truncatedoctahedron.gif"></p>
  * \sa CreateOctahedron,CreateTetrakisHexahedron
  */
  static CPolyhedron::Ptr CreateTruncatedOctahedron(double radius);
  /**
  * Creates a rhombicuboctahedron, with 18 squares and 8 triangles (see
  http://en.wikipedia.org/wiki/Rhombicuboctahedron), calculated as an
  elongated square bicupola. It can also be calculated as a cantellated
  hexahedron or octahedron, and its dual is the deltoidal icositetrahedron.
  * If the second argument is set to false, the lower cupola is rotated, so
  that the objet created is an elongated square gyrobicupola (see
  http://en.wikipedia.org/wiki/Elongated_square_gyrobicupola). This is not
  an archimedean solid, but a Johnson one, since it hasn't got vertex
  transitivity.
  <p align="center"><img src="Rhombicuboctahedron.gif"></p>
  * \sa
  CreateJohnsonSolidWithConstantBase,CreateHexahedron,CreateOctahedron,cantellate,CreateCuboctahedron,CreateDeltoidalIcositetrahedron
  */
  static CPolyhedron::Ptr CreateRhombicuboctahedron(double radius, bool type = true);
  /**
  * Creates an icosidodecahedron, with 12 pentagons and 20 triangles (see
  http://en.wikipedia.org/wiki/Icosidodecahedron). Certain truncations of
  either a dodecahedron or an icosahedron yield an icosidodecahedron.
  * The dual of the icosidodecahedron is the rhombic triacontahedron.
  * If the second argument is set to false, the lower rotunda is rotated. In
  this case, the object created is a pentagonal orthobirotunda (see
  http://en.wikipedia.org/wiki/Pentagonal_orthobirotunda). This object
  presents symmetry against the XY plane and is not vertex transitive, so
  it's a Johnson's solid.
  <p align="center"><img src="Icosidodecahedron.gif"></p>
  * \sa
  CreateDodecahedron,CreateIcosahedron,truncate,CreateRhombicosidodecahedron,CreateRhombicTriacontahedron
  */
  static CPolyhedron::Ptr CreateIcosidodecahedron(double radius, bool type = true);
  /**
  * Creates a truncated dodecahedron, consisting of 12 dodecagons and 20
  triangles (see http://en.wikipedia.org/wiki/Truncated_dodecahedron). The
  truncated dodecahedron is the dual to the triakis icosahedron.
  <p align="center"><img src="Truncateddodecahedron.gif"></p>
  * \sa CreateDodecahedron,CreateTriakisIcosahedron
  */
  static CPolyhedron::Ptr CreateTruncatedDodecahedron(double radius);
  /**
  * Creates a truncated icosahedron, consisting of 20 hexagons and 12
  pentagons. This object resembles a typical soccer ball (see
  http://en.wikipedia.org/wiki/Truncated_icosahedron). The pentakis
  dodecahedron is the dual to the truncated icosahedron.
  <p align="center"><img src="Truncatedicosahedron.gif"></p>
  * \sa CreateIcosahedron,CreatePentakisDodecahedron
  */
  static CPolyhedron::Ptr CreateTruncatedIcosahedron(double radius);
  /**
  * Creates a rhombicosidodecahedron, consisting of 30 squares, 12 pentagons
  and 20 triangles (see
  http://en.wikipedia.org/wiki/Rhombicosidodecahedron). This object can be
  obtained as the cantellation of either a dodecahedron or an icosahedron.
  The dual of the rhombicosidodecahedron is the deltoidal hexecontahedron.
  <p align="center"><img src="Rhombicosidodecahedron.gif"></p>
  * \sa
  CreateDodecahedron,CreateIcosahedron,CreateIcosidodecahedron,CreateDeltoidalHexecontahedron
  */
  static CPolyhedron::Ptr CreateRhombicosidodecahedron(double radius);
  /** @}
   */

  /** @name Other Johnson solids.
    @{
   */
  /**
   * Creates a pentagonal rotunda (half an icosidodecahedron), consisting of
   * six pentagons, ten triangles and a decagon (see
   * http://en.wikipedia.org/wiki/Pentagonal_rotunda).
   * \sa CreateIcosidodecahedron,CreateJohnsonSolidWithConstantBase
   */
  static CPolyhedron::Ptr CreatePentagonalRotunda(double radius);
  /** @}
   */

  /** @name Catalan solids.
    @{
  */
  /**
  * Creates a triakis tetrahedron, dual to the truncated tetrahedron. This
  body consists of 12 isosceles triangles (see
  http://en.wikipedia.org/wiki/Triakis_tetrahedron).
  <p align="center"><img src="Triakistetrahedron.gif"></p>
  * \sa CreateTruncatedTetrahedron
  */
  static CPolyhedron::Ptr CreateTriakisTetrahedron(double radius);

  /**
  * Creates a rhombic dodecahedron, dual to the cuboctahedron. This body
  consists of 12 rhombi (see
  http://en.wikipedia.org/wiki/Rhombic_dodecahedron).
  <p align="center"><img src="Rhombicdodecahedron.gif"></p>
  * \sa CreateCuboctahedron
  */
  static CPolyhedron::Ptr CreateRhombicDodecahedron(double radius);

  /**
  * Creates a triakis octahedron, dual to the truncated hexahedron. This
  body consists of 24 isosceles triangles (see
  http://en.wikipedia.org/wiki/Triakis_octahedron).
  <p align="center"><img src="Triakisoctahedron.gif"></p>
  * \sa CreateTruncatedHexahedron
  */
  static CPolyhedron::Ptr CreateTriakisOctahedron(double radius);

  /**
  * Creates a tetrakis hexahedron, dual to the truncated octahedron. This
  body consists of 24 isosceles triangles (see
  http://en.wikipedia.org/wiki/Tetrakis_hexahedron).
  <p align="center"><img src="Tetrakishexahedron.gif"></p>
  * \sa CreateTruncatedOctahedron
  */
  static CPolyhedron::Ptr CreateTetrakisHexahedron(double radius);

  /**
  * Creates a deltoidal icositetrahedron, dual to the rhombicuboctahedron.
  This body consists of 24 kites (see
  http://en.wikipedia.org/wiki/Deltoidal_icositetrahedron).
  <p align="center"><img src="Deltoidalicositetrahedron.gif"></p>
  * \sa CreateRhombicuboctahedron
  */
  static CPolyhedron::Ptr CreateDeltoidalIcositetrahedron(double radius);

  /**
  * Creates a rhombic triacontahedron, dual to the icosidodecahedron. This
  body consists of 30 rhombi (see
  http://en.wikipedia.org/wiki/Rhombic_triacontahedron).
  <p align="center"><img src="Rhombictriacontahedron.gif"></p>
  * \sa CreateIcosidodecahedron
  */
  static CPolyhedron::Ptr CreateRhombicTriacontahedron(double radius);

  /**
  * Creates a triakis icosahedron, dual to the truncated dodecahedron. This
  body consists of 60 isosceles triangles
  http://en.wikipedia.org/wiki/Triakis_icosahedron).
  <p align="center"><img src="Triakisicosahedron.gif"></p>
  * \sa CreateTruncatedDodecahedron
  */
  static CPolyhedron::Ptr CreateTriakisIcosahedron(double radius);

  /**
  * Creates a pentakis dodecahedron, dual to the truncated icosahedron. This
  body consists of 60 isosceles triangles (see
  http://en.wikipedia.org/wiki/Pentakis_dodecahedron).
  <p align="center"><img src="Pentakisdodecahedron.gif"></p>
  * \sa CreateTruncatedIcosahedron
  */
  static CPolyhedron::Ptr CreatePentakisDodecahedron(double radius);

  /**
  * Creates a deltoidal hexecontahedron, dual to the rhombicosidodecahedron.
  This body consists of 60 kites (see
  http://en.wikipedia.org/wiki/Deltoidal_hexecontahedron).
  <p align="center"><img src="Deltoidalhexecontahedron.gif"></p>
  * \sa CreateRhombicosidodecahedron
  */
  static CPolyhedron::Ptr CreateDeltoidalHexecontahedron(double radius);
  /** @}
   */

  /** @name Customizable polyhedra
    @{
   */
  /**
   * Creates a cubic prism, given the coordinates of two opposite vertices.
   * Each edge will be parallel to one of the coordinate axes, although the
   * orientation may change by assigning a pose to the object.
   * \sa CreateCubicPrism(const mrpt::math::TPoint3D &,const
   * mrpt::math::TPoint3D
   * &),CreateParallelepiped,CreateCustomPrism,CreateRegularPrism,CreateArchimedeanRegularPrism
   */
  static CPolyhedron::Ptr CreateCubicPrism(
      double x1, double x2, double y1, double y2, double z1, double z2);
  /**
   * Creates a cubic prism, given two opposite vertices.
   * \sa
   * CreateCubicPrism(double,double,double,double,double,double),CreateParallelepiped,CreateCustomPrism,CreateRegularPrism,CreateArchimedeanRegularPrism
   */
  static CPolyhedron::Ptr CreateCubicPrism(
      const mrpt::math::TPoint3D& p1, const mrpt::math::TPoint3D& p2);
  /**
   * Creates a custom pyramid, using a set of 2D vertices which will lie on
   * the XY plane.
   * \sa
   * CreateDoublePyramid,CreateFrustum,CreateBifrustum,CreateRegularPyramid
   */
  static CPolyhedron::Ptr CreatePyramid(
      const std::vector<mrpt::math::TPoint2D>& baseVertices, double height);
  /**
   * Creates a double pyramid, using a set of 2D vertices which will lie on
   * the XY plane. The second height is used with the downwards pointing
   * pyramid, so that it will effectively point downwards if it's positive.
   * \sa CreatePyramid,CreateBifrustum,CreateRegularDoublePyramid
   */
  static CPolyhedron::Ptr CreateDoublePyramid(
      const std::vector<mrpt::math::TPoint2D>& baseVertices, double height1, double height2);
  /**
   * Creates a truncated pyramid, using a set of vertices which will lie on
   * the XY plane.
   * Do not try to use with a ratio equal to zero; use CreatePyramid instead.
   * When using a ratio of 1, it will create a Prism.
   * \sa CreatePyramid,CreateBifrustum
   */
  static CPolyhedron::Ptr CreateTruncatedPyramid(
      const std::vector<mrpt::math::TPoint2D>& baseVertices, double height, double ratio);
  /**
   * This is a synonym for CreateTruncatedPyramid.
   * \sa CreateTruncatedPyramid
   */
  static CPolyhedron::Ptr CreateFrustum(
      const std::vector<mrpt::math::TPoint2D>& baseVertices, double height, double ratio);
  /**
   * Creates a custom prism with vertical edges, given any base which will
   * lie on the XY plane.
   * \sa
   * CreateCubicPrism,CreateCustomAntiprism,CreateRegularPrism,CreateArchimedeanRegularPrism
   */
  static CPolyhedron::Ptr CreateCustomPrism(
      const std::vector<mrpt::math::TPoint2D>& baseVertices, double height);
  /**
   * Creates a custom antiprism, using two custom bases. For better results,
   * the top base should be slightly rotated with respect to the bottom one.
   * \sa
   * CreateCustomPrism,CreateRegularAntiprism,CreateArchimedeanRegularAntiprism
   */
  static CPolyhedron::Ptr CreateCustomAntiprism(
      const std::vector<mrpt::math::TPoint2D>& bottomBase,
      const std::vector<mrpt::math::TPoint2D>& topBase,
      double height);
  /**
   * Creates a parallelepiped, given a base point and three vectors
   * represented as points.
   * \sa CreateCubicPrism
   */
  static CPolyhedron::Ptr CreateParallelepiped(
      const mrpt::math::TPoint3D& base,
      const mrpt::math::TPoint3D& v1,
      const mrpt::math::TPoint3D& v2,
      const mrpt::math::TPoint3D& v3);
  /**
   * Creates a bifrustum, or double truncated pyramid, given a base which
   * will lie on the XY plane.
   * \sa CreateFrustum,CreateDoublePyramid
   */
  static CPolyhedron::Ptr CreateBifrustum(
      const std::vector<mrpt::math::TPoint2D>& baseVertices,
      double height1,
      double ratio1,
      double height2,
      double ratio2);
  /**
   * Creates a trapezohedron, consisting of 2*N kites, where N is the number
   * of edges in the base. The base radius controls the polyhedron height,
   * whilst the distance between bases affects the height.
   * When the number of edges equals 3, the polyhedron is actually a
   * parallelepiped, and it can even be a cube.
   */
  static CPolyhedron::Ptr CreateTrapezohedron(
      uint32_t numBaseEdges, double baseRadius, double basesDistance);
  /**
   * Creates an antiprism whose base is a regular polygon. The upper base is
   * rotated \f$\frac\pi N\f$ with respect to the lower one, where N is the
   * number of vertices in the base, and thus the lateral triangles are
   * isosceles.
   * \sa CreateCustomAntiprism,CreateArchimedeanRegularAntiprism
   */
  static CPolyhedron::Ptr CreateRegularAntiprism(
      uint32_t numBaseEdges, double baseRadius, double height);
  /**
   * Creates a regular prism whose base is a regular polygon and whose edges
   * are either parallel or perpendicular to the XY plane.
   * \sa CreateCubicPrism,CreateCustomPrism,CreateArchimedeanRegularAntiprism
   */
  static CPolyhedron::Ptr CreateRegularPrism(
      uint32_t numBaseEdges, double baseRadius, double height);
  /**
   * Creates a regular pyramid whose base is a regular polygon.
   * \sa CreatePyramid
   */
  static CPolyhedron::Ptr CreateRegularPyramid(
      uint32_t numBaseEdges, double baseRadius, double height);
  /**
   * Creates a regular double pyramid whose base is a regular polygon.
   * \sa CreateDoublePyramid
   */
  static CPolyhedron::Ptr CreateRegularDoublePyramid(
      uint32_t numBaseEdges, double baseRadius, double height1, double height2);
  /**
   * Creates a regular prism whose lateral area is comprised of squares, and
   * so each face of its is a regular polygon. Due to vertex transitivity, the
   * resulting object is always archimedean.
   * \sa CreateRegularPrism,CreateCustomPrism
   */
  static CPolyhedron::Ptr CreateArchimedeanRegularPrism(uint32_t numBaseEdges, double baseRadius);
  /**
   * Creates a regular antiprism whose lateral polygons are equilateral
   * triangles, and so each face of its is a regular polygon. Due to vertex
   * transitivity, the resulting object is always archimedean.
   * \sa CreateRegularAntiprism,CreateCustomAntiprism
   */
  static CPolyhedron::Ptr CreateArchimedeanRegularAntiprism(
      uint32_t numBaseEdges, double baseRadius);
  /**
   * Creates a regular truncated pyramid whose base is a regular polygon.
   * \sa CreateTruncatedPyramid
   */
  static CPolyhedron::Ptr CreateRegularTruncatedPyramid(
      uint32_t numBaseEdges, double baseRadius, double height, double ratio);
  /**
   * This is a synonym for CreateRegularTruncatedPyramid.
   * \sa CreateRegularTruncatedPyramid
   */
  static CPolyhedron::Ptr CreateRegularFrustum(
      uint32_t numBaseEdges, double baseRadius, double height, double ratio);
  /**
   * Creates a bifrustum (double truncated pyramid) whose base is a regular
   * polygon lying in the XY plane.
   * \sa CreateBifrustum
   */
  static CPolyhedron::Ptr CreateRegularBifrustum(
      uint32_t numBaseEdges,
      double baseRadius,
      double height1,
      double ratio1,
      double height2,
      double ratio2);
  /**
   * Creates a cupola.
   * \throw std::logic_error if the number of edges is odd or less than four.
   */
  static CPolyhedron::Ptr CreateCupola(uint32_t numBaseEdges, double edgeLength);
  /**
   * Creates a trapezohedron whose dual is exactly an archimedean antiprism.
   * Creates a cube if numBaseEdges is equal to 3.
   * \todo Actually resulting height is significantly higher than that passed
   * to the algorithm.
   * \sa CreateTrapezohedron,CreateArchimedeanRegularAntiprism,getDual
   */
  static CPolyhedron::Ptr CreateCatalanTrapezohedron(uint32_t numBaseEdges, double height);
  /**
   * Creates a double pyramid whose dual is exactly an archimedean prism.
   * Creates an octahedron if numBaseEdges is equal to 4.
   * \todo Actually resulting height is significantly higher than that passed
   * to the algorithm.
   * \sa CreateDoublePyramid,CreateArchimedeanRegularPrism,getDual
   */
  static CPolyhedron::Ptr CreateCatalanDoublePyramid(uint32_t numBaseEdges, double height);
  /**
  * Creates a series of concatenated solids (most of which are prismatoids)
  whose base is a regular polygon with a given number of edges. Every face
  of the resulting body will be a regular polygon, so it is a Johnson solid;
  in special cases, it may be archimedean or even platonic.
  * The shape of the body is defined by the string argument, which can
  include one or more of the following:
  <center><table>
  <tr><td><b>String</b></td><td><b>Body</b></td><td><b>Restrictions</b></td></tr>
  <tr><td>P+</td><td>Upward pointing pyramid</td><td>Must be the last
  object, vertex number cannot surpass 5</td></tr>
  <tr><td>P-</td><td>Downward pointing pyramid</td><td>Must be the first
  object, vertex number cannot surpass 5</td></tr>
  <tr><td>C+</td><td>Upward pointing cupola</td><td>Must be the last object,
  vertex number must be an even number in the range 4-10.</td></tr>
  <tr><td>C-</td><td>Downward pointing cupola</td><td>Must be the first
  object, vertex number must be an even number in the range 4-10.</td></tr>
  <tr><td>GC+</td><td>Upward pointing cupola, rotated</td><td>Must be the
  last object, vertex number must be an even number in the range
  4-10.</td></tr>
  <tr><td>GC-</td><td>Downward pointing cupola, rotated</td><td>Must be the
  first object, vertex number must be an even number in the range
  4-10.</td></tr>
  <tr><td>PR</td><td>Archimedean prism</td><td>Cannot abut other
  prism</td></tr>
  <tr><td>A</td><td>Archimedean antiprism</td><td>None</td></tr>
  <tr><td>R+</td><td>Upward pointing rotunda</td><td>Must be the last
  object, vertex number must be exactly 10</td></tr>
  <tr><td>R-</td><td>Downward pointing rotunda</td><td>Must be the first
  object, vertex number must be exactly 10</td></tr>
  <tr><td>GR+</td><td>Upward pointing rotunda, rotated</td><td>Must be the
  last object, vertex number must be exactly 10</td></tr>
  <tr><td>GR-</td><td>Downward pointing rotunda</td><td>Must be the first
  object, vertex number must be exactly 10</td></tr>
  </table></center>
  * Some examples of bodies are:
  <center><table>
  <tr><td><b>String</b></td><td><b>Vertices</b></td><td><b>Resulting
  body</b></td></tr>
  <tr><td>P+</td><td align="center">3</td><td>Tetrahedron</td></tr>
  <tr><td>PR</td><td align="center">4</td><td>Hexahedron</td></tr>
  <tr><td>P-P+</td><td align="center">4</td><td>Octahedron</td></tr>
  <tr><td>A</td><td align="center">3</td><td>Octahedron</td></tr>
  <tr><td>C+PRC-</td><td
  align="center">8</td><td>Rhombicuboctahedron</td></tr>
  <tr><td>P-AP+</td><td align="center">5</td><td>Icosahedron</td></tr>
  <tr><td>R-R+</td><td align="center">10</td><td>Icosidodecahedron</td></tr>
  </table></center>
  */
  static CPolyhedron::Ptr CreateJohnsonSolidWithConstantBase(
      uint32_t numBaseEdges, double baseRadius, const std::string& components, size_t shifts = 0);
  /** @}
   */

  bool traceRay(const mrpt::poses::CPose3D& o, double& dist) const override;

  /**
   * Gets a list with the polyhedron's vertices.
   */
  inline void getVertices(std::vector<mrpt::math::TPoint3D>& vertices) const
  {
    vertices = m_Vertices;
  }
  /**
   * Gets a list with the polyhedron's edges.
   */
  inline void getEdges(std::vector<TPolyhedronEdge>& edges) const { edges = m_Edges; }
  /**
   * Gets a list with the polyhedron's faces.
   */
  inline void getFaces(std::vector<TPolyhedronFace>& faces) const { faces = m_Faces; }
  /**
   * Gets the amount of vertices.
   */
  inline uint32_t getNumberOfVertices() const { return m_Vertices.size(); }
  /**
   * Gets the amount of edges.
   */
  inline uint32_t getNumberOfEdges() const { return m_Edges.size(); }
  /**
   * Gets the amount of faces.
   */
  inline uint32_t getNumberOfFaces() const { return m_Faces.size(); }
  /**
   * Gets a vector with each edge's length.
   */
  void getEdgesLength(std::vector<double>& lengths) const;
  /**
   * Gets a vector with each face's area. Won't work properly if the polygons
   * are not convex.
   */
  void getFacesArea(std::vector<double>& areas) const;
  /**
   * Gets the polyhedron volume. Won't work properly if the polyhedron is not
   * convex.
   */
  double getVolume() const;
  /**
   * Returns whether the polyhedron will be rendered as a wireframe object.
   */
  inline bool isWireframe() const { return m_Wireframe; }
  /**
   * Sets whether the polyhedron will be rendered as a wireframe object.
   */
  inline void setWireframe(bool enabled = true)
  {
    m_Wireframe = enabled;
    CRenderizable::notifyChange();
  }
  /**
   * Gets the polyhedron as a set of polygons.
   * \sa mrpt::math::TPolygon3D
   */
  void getSetOfPolygons(std::vector<math::TPolygon3D>& vec) const;
  /**
   * Gets the polyhedron as a set of polygons, with the pose transformation
   * already applied.
   * \sa mrpt::math::TPolygon3D,mrpt::poses::CPose3D
   */
  void getSetOfPolygonsAbsolute(std::vector<math::TPolygon3D>& vec) const;
  /** Gets the intersection of two polyhedra, either as a set or as a matrix
   * of intersections. Each intersection is represented by a TObject3D.
   * \sa mrpt::math::TObject3D
   */
  template <class T>
  inline static size_t getIntersection(
      const CPolyhedron::Ptr& p1, const CPolyhedron::Ptr& p2, T& container);
  /**
   * Returns true if the polygon is a completely closed object.
   */
  inline bool isClosed() const
  {
    for (size_t i = 0; i < m_Vertices.size(); i++)
      if (edgesInVertex(i) != facesInVertex(i)) return false;
    return true;
  }
  /**
   * Recomputes polygons, if necessary, so that each one is convex.
   */
  void makeConvexPolygons();
  /**
   * Gets the center of the polyhedron.
   */
  void getCenter(mrpt::math::TPoint3D& center) const;
  /**
   * Creates a random polyhedron from the static methods.
   */
  static CPolyhedron::Ptr CreateRandomPolyhedron(double radius);

  /** @name Polyhedron special operations.
    @{
   */
  /**
   * Given a polyhedron, creates its dual.
   * \sa truncate,cantellate,augment
   * \throw std::logic_error Can't get the dual to this polyhedron.
   */
  CPolyhedron::Ptr getDual() const;
  /**
   * Truncates a polyhedron to a given factor.
   * \sa getDual,cantellate,augment
   * \throw std::logic_error Polyhedron truncation results in skew polygons
   * and thus it's impossible to perform.
   */
  CPolyhedron::Ptr truncate(double factor) const;
  /**
   * Cantellates a polyhedron to a given factor.
   * \sa getDual,truncate,augment
   */
  CPolyhedron::Ptr cantellate(double factor) const;
  /**
   * Augments a polyhedron to a given height. This operation is roughly dual
   * to the truncation: given a body P, the operation dtdP and aP yield
   * resembling results.
   * \sa getDual,truncate,cantellate
   */
  CPolyhedron::Ptr augment(double height) const;
  /**
   * Augments a polyhedron to a given height. This method only affects to
   * faces with certain number of vertices.
   * \sa augment(double) const
   */
  CPolyhedron::Ptr augment(double height, size_t numVertices) const;
  /**
   * Augments a polyhedron, so that the resulting triangles are equilateral.
   * If the argument is true, triangles are "cut" from the polyhedron, instead
   * of being added.
   * \throw std::logic_error a non-regular face has been found.
   * \sa augment(double) const
   */
  CPolyhedron::Ptr augment(bool direction = false) const;
  /**
   * Augments a polyhedron, so that the resulting triangles are equilateral;
   * affects only faces with certain number of faces. If the second argument
   * is true, triangles are "cut" from the polyhedron.
   * \throw std::logic_error a non-regular face has been found.
   * \sa augment(double) const
   */
  CPolyhedron::Ptr augment(size_t numVertices, bool direction = false) const;
  /**
   * Rotates a polyhedron around the Z axis a given amount of radians. In
   *some cases, this operation may be necessary to view the symmetry between
   *related objects.
   *	\sa scale
   */
  CPolyhedron::Ptr rotate(double angle) const;
  /**
   * Scales a polyhedron to a given factor.
   * \throw std::logic_error factor is not a strictly positive number.
   * \sa rotate
   */
  CPolyhedron::Ptr scale(double factor) const;
  /** @}
   */
  /**
   * Updates the mutable list of polygons used in rendering and ray tracing.
   */
  void updatePolygons() const;

 private:
  /**
   * Generates a list of 2D vertices constituting a regular polygon.
   */
  static std::vector<mrpt::math::TPoint2D> generateBase(uint32_t numBaseEdges, double baseRadius);
  /**
   * Generates a list of 2D vertices constituting a regular polygon, with an
   * angle shift which makes it suitable for antiprisms.
   */
  static std::vector<mrpt::math::TPoint2D> generateShiftedBase(
      uint32_t numBaseEdges, double baseRadius);
  /**
   * Generates a list of 3D vertices constituting a regular polygon,
   * appending it to an existing vector.
   */
  static void generateBase(
      uint32_t numBaseEdges,
      double baseRadius,
      double height,
      std::vector<mrpt::math::TPoint3D>& vec);
  /**
   * Generates a list of 3D vertices constituting a regular polygon
   * conveniently shifted, appending it to an existing vector.
   */
  static void generateShiftedBase(
      uint32_t numBaseEdges,
      double baseRadius,
      double height,
      double shift,
      std::vector<mrpt::math::TPoint3D>& vec);
  /**
   * Calculates the normal vector to a face.
   */
  bool setNormal(TPolyhedronFace& f, bool doCheck = true);
  /**
   * Adds, to the existing list of edges, each edge in a given face.
   */
  void addEdges(const TPolyhedronFace& e);
  /**
   * Checks whether a set of faces is suitable for a set of vertices.
   */
  static bool checkConsistence(
      const std::vector<mrpt::math::TPoint3D>& vertices, const std::vector<TPolyhedronFace>& faces);
  /**
   * Returns how many edges converge in a given vertex.
   */
  size_t edgesInVertex(size_t vertex) const;
  /**
   * Returns how many faces converge in a given vertex.
   */
  size_t facesInVertex(size_t vertex) const;

 public:
  /**
   * Basic empty constructor.
   */
  inline CPolyhedron() : m_Vertices(), m_Edges(), m_Faces() {}
  /**
   * Basic constructor with a list of vertices and another of faces, checking
   * for correctness.
   */
  inline CPolyhedron(
      const std::vector<mrpt::math::TPoint3D>& vertices,
      const std::vector<TPolyhedronFace>& faces,
      bool doCheck = true) :
      m_Vertices(vertices), m_Edges(), m_Faces(faces), m_Wireframe(false), polygonsUpToDate(false)
  {
    InitFromVertAndFaces(vertices, faces, doCheck);
  }
  inline void InitFromVertAndFaces(
      const std::vector<mrpt::math::TPoint3D>& vertices,
      const std::vector<TPolyhedronFace>& faces,
      bool doCheck = true)
  {
    if (doCheck && !checkConsistence(vertices, faces))
      throw std::logic_error("Face list accesses a vertex out of range");
    for (auto& mFace : m_Faces)
    {
      if (!setNormal(mFace, doCheck)) throw std::logic_error("Bad face specification");
      addEdges(mFace);
    }
  }

  CPolyhedron(const std::vector<math::TPolygon3D>& polys);

  CPolyhedron(
      const std::vector<mrpt::math::TPoint3D>& vertices,
      const std::vector<std::vector<uint32_t>>& faces);

  /** Creates a polyhedron without checking its correctness. */
  static CPolyhedron::Ptr CreateNoCheck(
      const std::vector<mrpt::math::TPoint3D>& vertices, const std::vector<TPolyhedronFace>& faces);
  /** Creates an empty Polyhedron. */
  static CPolyhedron::Ptr CreateEmpty();
  /** Destructor. */
  ~CPolyhedron() override = default;
};

// Implemented after the definition of Smart::Ptrs in the _POST() macro above.
template <class T>
size_t CPolyhedron::getIntersection(
    const CPolyhedron::Ptr& p1, const CPolyhedron::Ptr& p2, T& container)
{
  std::vector<mrpt::math::TPolygon3D> polys1, polys2;
  p1->getSetOfPolygonsAbsolute(polys1);
  p2->getSetOfPolygonsAbsolute(polys2);
  return mrpt::math::intersect(polys1, polys2, container);
}

/**
 * Reads a polyhedron edge from a binary stream.
 */
mrpt::serialization::CArchive& operator>>(
    mrpt::serialization::CArchive& in, CPolyhedron::TPolyhedronEdge& o);
/**
 * Writes a polyhedron edge to a binary stream.
 */
mrpt::serialization::CArchive& operator<<(
    mrpt::serialization::CArchive& out, const CPolyhedron::TPolyhedronEdge& o);
/**
 * Reads a polyhedron face from a binary stream.
 */
mrpt::serialization::CArchive& operator>>(
    mrpt::serialization::CArchive& in, CPolyhedron::TPolyhedronFace& o);
/**
 * Writes a polyhedron face to a binary stream.
 */
mrpt::serialization::CArchive& operator<<(
    mrpt::serialization::CArchive& out, const CPolyhedron::TPolyhedronFace& o);
}  // namespace opengl
namespace typemeta
{
// Specialization must occur in the same namespace
MRPT_DECLARE_TTYPENAME_NAMESPACE(CPolyhedron::TPolyhedronEdge, mrpt::opengl)
MRPT_DECLARE_TTYPENAME_NAMESPACE(CPolyhedron::TPolyhedronFace, mrpt::opengl)
}  // namespace typemeta
}  // namespace mrpt
