/* +------------------------------------------------------------------------+
   |                     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/core/bits_math.h>
#include <mrpt/core/exceptions.h>
#include <mrpt/img/TColor.h>
#include <mrpt/math/TPoint2D.h>
#include <mrpt/math/TPoint3D.h>
#include <mrpt/math/TPolygon3D.h>
#include <mrpt/serialization/serialization_frwds.h>

#include <array>

namespace mrpt::opengl
{
// Ensure 1-byte memory alignment, no additional stride bytes.
#pragma pack(push, 1)

/** A triangle (float coordinates) with RGBA colors (u8) and UV (texture
 * coordinates) for each vertex. Note that not all the fields must be filled in,
 * it depends on the consumer of the structure.
 *
 * The structure is memory packed to 1-byte, to ensure it can be used in GPU
 * memory vertex arrays without unexpected paddings.
 *
 * \sa opengl::Scene, CSetOfTexturedTriangles
 * \ingroup mrpt_opengl_grp
 */
struct TTriangle
{
  struct Vertex
  {
    mrpt::math::TPointXYZfRGBAu8 xyzrgba;
    mrpt::math::TVector3Df normal;  //!< Must not be normalized
    mrpt::math::TVector2Df uv;      //!< texture coordinates (0,0)-(1,1)

    void setColor(const mrpt::img::TColor& c)
    {
      xyzrgba.r = c.R;
      xyzrgba.g = c.G;
      xyzrgba.b = c.B;
      xyzrgba.a = c.A;
    }
  };

  TTriangle() = default;
  explicit TTriangle(const mrpt::math::TPolygon3D& p)
  {
    ASSERT_EQUAL_(p.size(), 3U);
    for (size_t i = 0; i < 3; i++)
    {
      auto& pp = vertices[i].xyzrgba;
      pp.pt = p[i].cast<float>();
      pp.r = pp.g = pp.b = 0xff;
    }
    computeNormals();
  }
  /** Constructor from 3 points (default normals are computed) */
  explicit TTriangle(
      const mrpt::math::TPoint3Df& p1,
      const mrpt::math::TPoint3Df& p2,
      const mrpt::math::TPoint3Df& p3)
  {
    vertices[0].xyzrgba.pt = p1;
    vertices[1].xyzrgba.pt = p2;
    vertices[2].xyzrgba.pt = p3;
    computeNormals();
  }
  /** Constructor from 3 points and its 3 normals */
  explicit TTriangle(
      const mrpt::math::TPoint3Df& p1,
      const mrpt::math::TPoint3Df& p2,
      const mrpt::math::TPoint3Df& p3,
      const mrpt::math::TVector3Df& n1,
      const mrpt::math::TVector3Df& n2,
      const mrpt::math::TVector3Df& n3)
  {
    vertices[0].xyzrgba.pt = p1;
    vertices[0].normal = n1;
    vertices[1].xyzrgba.pt = p2;
    vertices[1].normal = n2;
    vertices[2].xyzrgba.pt = p3;
    vertices[2].normal = n3;
  }

  std::array<Vertex, 3> vertices;

  const float& x(size_t i) const { return vertices[i].xyzrgba.pt.x; }
  const float& y(size_t i) const { return vertices[i].xyzrgba.pt.y; }
  const float& z(size_t i) const { return vertices[i].xyzrgba.pt.z; }
  const uint8_t& r(size_t i) const { return vertices[i].xyzrgba.r; }
  const uint8_t& g(size_t i) const { return vertices[i].xyzrgba.g; }
  const uint8_t& b(size_t i) const { return vertices[i].xyzrgba.b; }
  const uint8_t& a(size_t i) const { return vertices[i].xyzrgba.a; }
  const float& u(size_t i) const { return vertices[i].uv.x; }
  const float& v(size_t i) const { return vertices[i].uv.y; }
  float& x(size_t i) { return vertices[i].xyzrgba.pt.x; }
  float& y(size_t i) { return vertices[i].xyzrgba.pt.y; }
  float& z(size_t i) { return vertices[i].xyzrgba.pt.z; }
  uint8_t& r(size_t i) { return vertices[i].xyzrgba.r; }
  uint8_t& g(size_t i) { return vertices[i].xyzrgba.g; }
  uint8_t& b(size_t i) { return vertices[i].xyzrgba.b; }
  uint8_t& a(size_t i) { return vertices[i].xyzrgba.a; }
  float& u(size_t i) { return vertices[i].uv.x; }
  float& v(size_t i) { return vertices[i].uv.y; }

  mrpt::math::TPoint3Df& vertex(size_t i) { return vertices[i].xyzrgba.pt; }
  const mrpt::math::TPoint3Df& vertex(size_t i) const { return vertices[i].xyzrgba.pt; }

  /** Sets the color of all vertices */
  inline void setColor(const mrpt::img::TColor& c)
  {
    for (size_t i = 0; i < 3; i++)
    {
      vertices[i].xyzrgba.r = c.R;
      vertices[i].xyzrgba.g = c.G;
      vertices[i].xyzrgba.b = c.B;
      vertices[i].xyzrgba.a = c.A;
    }
  }
  inline void setColor(const mrpt::img::TColorf& c)
  {
    setColor(mrpt::img::TColor(f2u8(c.R), f2u8(c.G), f2u8(c.B), f2u8(c.A)));
  }

  /** Compute the three normals from the cross-product of "v01 x v02".
   * Note that using this default normals will not lead to interpolated
   * lighting in the fragment shaders, since all vertex are equal; a derived
   * class should use custom, more accurate normals to enable soft lighting.
   */
  void computeNormals();

  // These methods are explicitly instantiated for T=float and double.
  void readFrom(mrpt::serialization::CArchive& i);
  void writeTo(mrpt::serialization::CArchive& o) const;
};
#pragma pack(pop)

}  // namespace mrpt::opengl
