text: #ifndef MESH_H
  #define MESH_H
  
  #include <glad/glad.h> // holds all OpenGL type declarations
  
  #include <glm/glm.hpp>
  #include <glm/gtc/matrix_transform.hpp>
  
  #include <shader_s.h>
  
  #include <string>
  #include <vector>
  using namespace std;
  
  struct Vertex {
      // position
      glm::vec3 Position;
      // normal
      glm::vec3 Normal;
      // texCoords
      glm::vec2 TexCoords;
      // tangent
      glm::vec3 Tangent;
      // bitangent
      glm::vec3 Bitangent;
  };
  
  struct Texture {
      unsigned int id;
      string type;
      string path;
  };
  
  class Mesh {
  public:
      // mesh Data
      vector<Vertex>       vertices;
      vector<unsigned int> indices;
      vector<Texture>      textures;
      unsigned int VAO;
  
      // constructor
      Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
      {
          this->vertices = vertices;
          this->indices = indices;
          this->textures = textures;
  
          // now that we have all the required data, set the vertex buffers and its attribute pointers.
          setupMesh();
      }
  
      // render the mesh
      void Draw(Shader &shader) 
      {
          // bind appropriate textures
          unsigned int diffuseNr  = 1;
          unsigned int specularNr = 1;
          unsigned int normalNr   = 1;
          unsigned int heightNr   = 1;
          for(unsigned int i = 0; i < textures.size(); i++)
          {
              glActiveTexture(GL_TEXTURE0 + i); // active proper texture unit before binding
              // retrieve texture number (the N in diffuse_textureN)
              string number;
              string name = textures[i].type;
              if(name == "materaial.texture_diffuse")
                  number = std::to_string(diffuseNr++);
              else if(name == "materaial.texture_specular")
                  number = std::to_string(specularNr++); // transfer unsigned int to stream
              else if(name == "materaial.texture_normal")
                  number = std::to_string(normalNr++); // transfer unsigned int to stream
               else if(name == "materaial.texture_height")
                  number = std::to_string(heightNr++); // transfer unsigned int to stream
  
              // now set the sampler to the correct texture unit
              glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i);
              // and finally bind the texture
              glBindTexture(GL_TEXTURE_2D, textures[i].id);
          }
          
          // draw mesh
          glBindVertexArray(VAO);
          glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
          glBindVertexArray(0);
  
          // always good practice to set everything back to defaults once configured.
          glActiveTexture(GL_TEXTURE0);
      }
  
  private:
      // render data 
      unsigned int VBO, EBO;
  
      // initializes all the buffer objects/arrays
      void setupMesh()
      {
          // create buffers/arrays
          glGenVertexArrays(1, &VAO);
          glGenBuffers(1, &VBO);
          glGenBuffers(1, &EBO);
  
          glBindVertexArray(VAO);
          // load data into vertex buffers
          glBindBuffer(GL_ARRAY_BUFFER, VBO);
          // A great thing about structs is that their memory layout is sequential for all its items.
          // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
          // again translates to 3/2 floats which translates to a byte array.
          glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);  
  
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
          glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
  
          // set the vertex attribute pointers
          // vertex Positions
          glEnableVertexAttribArray(0);	
          glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
          // vertex normals
          glEnableVertexAttribArray(1);	
          glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
          // vertex texture coords
          glEnableVertexAttribArray(2);	
          glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
          // vertex tangent
          //glEnableVertexAttribArray(3);
          //glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent));
          // vertex bitangent
          //glEnableVertexAttribArray(4);
          //glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent));
  
          glBindVertexArray(0);
      }
  };
  #endif