
#include "Tflag_med.h"


// -----------------------
// --- le constructeur ---
// -----------------------

Tflag_med::Tflag_med()
{
    m_flag.tex_image = 0;
    //m_flag.tex_bump = 0;
    m_flag.vao = 0;
    m_flag.vao_compute = 0;

}

// ----------------------
// --- le destructeur ---
// ----------------------

Tflag_med::~Tflag_med()
{
    _clear();
}

// --------------------
// --- les méthodes ---
// --------------------


void Tflag_med::addModel(std::string flux)
{
    std::vector<std::string> strList;
    size_t idx = m_pos.size();

    ECO_strCut(strList, flux, ' ');

    m_pos.push_back(OVec3_f());
    m_rot.push_back(OVec3_f());
    m_scale.push_back(OVec3_f());
    m_model_multi.push_back(OMat4());

    m_flag.model_multi.push_back(OMat4());

    m_pos[idx].x = ECO_strToFloat(strList[0]);
    m_pos[idx].y = ECO_strToFloat(strList[1]);
    m_pos[idx].z = ECO_strToFloat(strList[2]);
    m_rot[idx].x = ECO_strToFloat(strList[3]);
    m_rot[idx].y = ECO_strToFloat(strList[4]);
    m_rot[idx].z = ECO_strToFloat(strList[5]);
    m_scale[idx].x = ECO_strToFloat(strList[6]);
        m_scale[idx].z = m_scale[idx].y = m_scale[idx].x;

    _setModel(idx);
}





void Tflag_med::_clear()
{
    m_pos.clear();
    m_rot.clear();
    m_scale.clear();
    m_model_multi.clear();

    m_flag.model_multi.clear();

    if (m_flag.tex_image > 0) glDeleteTextures(1, &m_flag.tex_image);
    //if (m_flag.tex_bump > 0) glDeleteTextures(1, &m_flag.tex_bump);

    if (m_flag.vao_buffer[0] != 0) glDeleteBuffers(nbr_vao_buffer, m_flag.vao_buffer);
    if (m_flag.vao != 0) glDeleteVertexArrays(1, &m_flag.vao);
    m_flag.vao = 0;

    if (m_flag.vao_compute_buffer[0] != 0) glDeleteBuffers(nbr_vao_compute_buffer, m_flag.vao_compute_buffer);
    if (m_flag.vao_compute != 0) glDeleteVertexArrays(1, &m_flag.vao_compute);
    m_flag.vao_compute = 0;
}

void Tflag_med::create(std::string file)
{
    std::vector<std::string> strList;

    ECO_strCut(strList, file, ' ');

    dshFlag.create();
    dshFlag_cpt0.create();
    dshFlag_cpt1.create();
    //dshFlag_norm.create();

    // ---

    _clear();

    m_pos.push_back(OVec3_f());
    m_rot.push_back(OVec3_f());
    m_scale.push_back(OVec3_f());
    m_model_multi.push_back(OMat4());

    m_flag.model_multi.push_back(OMat4());

    // ---

    m_code = strList[0];
	m_path = strList[1];
	m_file = strList[2];
	// 3
	m_pos[0].x = ECO_strToFloat(strList[4]);
	m_pos[0].y = ECO_strToFloat(strList[5]);
	m_pos[0].z = ECO_strToFloat(strList[6]);
	m_rot[0].x = ECO_strToFloat(strList[7]);
	m_rot[0].y = ECO_strToFloat(strList[8]);
	m_rot[0].z = ECO_strToFloat(strList[9]);
    m_scale[0].x = ECO_strToFloat(strList[10]);
        m_scale[0].z = m_scale[0].y = m_scale[0].x;
	// 11
	m_flag.decal_pos.x = ECO_strToFloat(strList[12]);
	m_flag.decal_pos.y = m_flag.decal_pos.x * -2; // ECO_strToFloat(strList[13]); // compensation du decalage de rotation shader
	m_flag.decal_pos.z = ECO_strToFloat(strList[14]);
    // 15
    m_flag.width       = ECO_strToFloat(strList[16]);
    m_flag.height      = ECO_strToFloat(strList[17]);
    m_flag.nbCol       = ECO_strToInt(strList[18]);
    m_flag.nbRow       = ECO_strToInt(strList[19]);
    //
    m_flag.img_tex     = strList[20];
    //m_flag.img_bump    = strList[21];
    // 21

    _composition();
    _setModel(0);


}

void Tflag_med::_composition()
{
    std::string file;

    OMesh_obj::P_setPath(PATH_MESH);

    if (m_file.find(".obj"))
    {
        if (m_path != "null")
        {
            file = m_path + m_file;
        }
        else
        {
            file = m_file;
        }
        if (!mesh.create(file))
        {
            ECO_error_set("echec chargement %s >Tflag_med::_composition", m_file.c_str());
            return;
        }
    }
    else
    {
        ECO_error_set("erreur fichier %s >Tflag_med", m_file.c_str());
        return;
    }

    // ---

    _generate_flag();
    _make_vao_compute(); // avant !
    _make_vao();

}





void Tflag_med::draw(const OMat4& vp, ODshGeo& dshGeo)
{
    glDisable(GL_CULL_FACE);

    // --- pole

    dshGeo.enable();
    dshGeo.setMulti(true);

    for (size_t i_par = 0; i_par < mesh.getPart_size(); i_par++)
    {
        dshGeo.setTextures(mesh.getId_diffuseMap(i_par), mesh.getId_normalMap(i_par), mesh.getId_specularMap(i_par) ) ;
        mesh.draw_multi(i_par);
    }
    mesh.end_draw_part();

    dshGeo.setMulti(false);

    // --- flag

    glBindVertexArray(m_flag.vao_compute);

    // 1. Intégration
    dshFlag_cpt0.enable();
    dshFlag_cpt0.dispatchCompute( m_flag.groups, 1, 1);
    dshFlag_cpt0.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
    dshFlag_cpt0.disable();

    // 2. Contraintes (itérées)
    dshFlag_cpt1.enable();
    //for (int i = 0; i < 8; ++i) {
    for (int i = 0; i < 4; ++i) {
        dshFlag_cpt1.dispatchCompute( m_flag.groups, 1, 1);
        dshFlag_cpt1.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
    }
    dshFlag_cpt1.disable();

    // NOTE : pour ameliorer l'affichage du drapeau, il faut ajouter un compute normals
    // et pour un drapeau realiste : normals sur les deux faces (+shader),
    // ajouter un bending, stabiliser les contraintes, ajouter un bump...

/*
    // recalcul des normales
    dshFlag_norm.enable();
    dshFlag_norm.dispatchCompute(m_flag.groups, 1, 1);
    dshFlag_norm.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
    dshFlag_norm.disable();
*/
    // --- shader affichage

    dshFlag.enable();
    dshFlag.setVP(vp);
    dshFlag.setTexture(m_flag.tex_image);

    // toujours multi
    glBindVertexArray(m_flag.vao);
        glDrawElementsInstanced(GL_TRIANGLES, m_flag.indice.size(), GL_UNSIGNED_INT, 0, m_flag.model_multi.size() );
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}





void Tflag_med::_generate_flag()
{
    if (m_flag.nbCol < 4 || m_flag.nbRow < 4)
    {
        ECO_error_set("err nbCol nbRow >Tflag_med.generate_flag");
        m_flag.nbCol = 4;
        m_flag.nbRow = 4;
    }
    if (m_flag.width < 0.2f || m_flag.height < 0.2f)
    {
        ECO_error_set("err width height >Tflag_med.generate_flag");
        m_flag.width = 0.2f;
        m_flag.height = 0.2f;
    }

    size_t idx = 0;
    int numIndice = 0;

    float spaceX = m_flag.width  / (m_flag.nbCol - 1); // ecart horizontal
    float spaceZ = m_flag.height / (m_flag.nbRow - 1); // ecart vertical
    //ECO_error_set("space %f %f", spaceX, spaceZ);
    m_flag.vertex.resize(m_flag.nbCol * m_flag.nbRow);
    m_flag.normal.resize(m_flag.nbCol * m_flag.nbRow);
    m_flag.uv.resize(m_flag.nbCol * m_flag.nbRow);
    // m_flag.indice
    m_flag.cpt_pos.resize(m_flag.nbCol * m_flag.nbRow);
    m_flag.cpt_pos_prev.resize(m_flag.nbCol * m_flag.nbRow);

    idx = 0;
	for (int y = 0; y < m_flag.nbRow; y++)
    {
        for (int x = 0; x < m_flag.nbCol; x++)
		{
            m_flag.vertex[idx].set(x * spaceX, 0.0f, -y * spaceZ);
            m_flag.normal[idx].set(0.0f, 0.0f, 1.0f); // z verticale
            m_flag.uv[idx].set( x / float(m_flag.nbCol-1), y / float(m_flag.nbRow-1) );

            m_flag.cpt_pos[idx].set(x * spaceX, 0.0f, -y * spaceZ, (x == 0) ? 0.0f : 1.0f);
            m_flag.cpt_pos_prev[idx] = m_flag.cpt_pos[idx];
            m_flag.cpt_pos_prev[idx].w = 0.0f;
            idx++;
		}
    }

    // ---

    numIndice = m_flag.nbCol * m_flag.nbRow * 3 * 2;
    m_flag.indice.resize(numIndice);

	for (int y = 0; y < m_flag.nbRow-1; y++)
    {
        for (int x = 0; x < m_flag.nbCol-1; x++)
		{
            int arrayPos = 6*(y*m_flag.nbCol+x);

            m_flag.indice[arrayPos+0] = y * m_flag.nbCol + x;
			m_flag.indice[arrayPos+1] = y * m_flag.nbCol + x + 1;
			m_flag.indice[arrayPos+2] = (y+1) * m_flag.nbCol + x + 1;

			m_flag.indice[arrayPos+3] = (y+1) * m_flag.nbCol + x + 1;
			m_flag.indice[arrayPos+4] = (y+1) * m_flag.nbCol + x;
			m_flag.indice[arrayPos+5] = y * m_flag.nbCol + x;
        }
    }

    // ---

    std::string file0, file1;
    if (m_path != "null")
    {
        file0 = PATH_MESH + m_path + m_flag.img_tex;
        //file1 = PATH_MESH + m_path + m_flag.img_bump;
    }
    else
    {
        file0 = PATH_MESH + m_flag.img_tex;
        //file1 = PATH_MESH + m_flag.img_bump;
    }

    if (m_flag.tex_image > 0) glDeleteTextures(1, &m_flag.tex_image);
    //if (m_flag.tex_bump > 0) glDeleteTextures(1, &m_flag.tex_bump);
    m_flag.tex_image = _load_texture(file0);
	//m_flag.tex_bump  = _load_texture(file1);

    // ---

    m_flag.groups = ((m_flag.nbCol * m_flag.nbRow) + 255) / 256;

    dshFlag_cpt0.enable();
        dshFlag_cpt0.setColRow(m_flag.nbCol, m_flag.nbRow);
    dshFlag_cpt0.disable();

    dshFlag_cpt1.enable();
        dshFlag_cpt1.setRestLength(spaceX);
        dshFlag_cpt1.setColRow(m_flag.nbCol, m_flag.nbRow);
    dshFlag_cpt1.disable();

    /*
    dshFlag_norm.enable();
        dshFlag_norm.setColRow(m_flag.nbCol, m_flag.nbRow);
    dshFlag_norm.disable();
    */
}

/*
// Génère le maillage d'un drapeau avec faces avant/arrière, normales et UVs
void Tflag_med::_generate_flash_double_face()
{
    // Pas entre chaque sommet en largeur et hauteur
    float step_w = m_flag.width / (m_flag.nbCol - 1);
    float step_h = m_flag.height / (m_flag.nbrRow - 1);

    // Génération des sommets
    for (int r = 0; r < m_flag.nbrRow; ++r)
    {
        for (int c = 0; c < m_flag.nbCol; ++c)
        {
            // Position du sommet (centré en (0,0,0))
            float x = c * step_w - m_flag.width / 2.0f;
            float y = r * step_h - m_flag.height / 2.0f;
            float z = 0.0f;

            // Normale pour la face avant (vers l'avant)
            float nx_front = 0.0f;
            float ny_front = 0.0f;
            float nz_front = 1.0f;

            // Normale pour la face arrière (vers l'arrière)
            float nx_back = 0.0f;
            float ny_back = 0.0f;
            float nz_back = -1.0f;

            // Coordonnées de texture (UV) : de 0 à 1 sur toute la surface
            float u = static_cast<float>(c) / (m_flag.nbCol - 1);
            float v = static_cast<float>(r) / (m_flag.nbrRow - 1);

            // Ajout du sommet pour la face avant
            m_flag.vertex.push_back( OVec3_f(x, y, z) );
            m_flag.normal.push_back( OVec3_f(nx_front, ny_front, nz_front) )
            m_flag.uv.push_back( OVec2_f(u, v) );

            // Ajout du sommet pour la face arrière
            m_flag.vertex.push_back( OVec3_f(x, y, z) );
            m_flag.normal.push_back( OVec3_f(nx_back, ny_back, nz_back) );
            m_flag.uv.push_back( OVec2_f(u, v) );
        }
    }

    // Génération des indices (triangles)
    for (int r = 0; r < m_flag.nbrRow - 1; ++r)
    {
        for (int c = 0; c < m_flag.nbCol - 1; ++c)
        {
            // Indices pour la face avant
            int i0 = r * m_flag.nbCol * 2 + c * 2;
            int i1 = r * m_flag.nbCol * 2 + (c + 1) * 2;
            int i2 = (r + 1) * m_flag.nbCol * 2 + c * 2;
            int i3 = (r + 1) * m_flag.nbCol * 2 + (c + 1) * 2;

            // Triangle 1 (face avant)
            m_flag.indice.push_back(i0);
            m_flag.indice.push_back(i1);
            m_flag.indice.push_back(i2);

            // Triangle 2 (face avant)
            m_flag.indice.push_back(i2);
            m_flag.indice.push_back(i1);
            m_flag.indice.push_back(i3);

            // Indices pour la face arrière (décalage de 1)
            int i4 = r * m_flag.nbCol * 2 + c * 2 + 1;
            int i5 = r * m_flag.nbCol * 2 + (c + 1) * 2 + 1;
            int i6 = (r + 1) * m_flag.nbCol * 2 + c * 2 + 1;
            int i7 = (r + 1) * m_flag.nbCol * 2 + (c + 1) * 2 + 1;

            // Triangle 1 (face arrière)
            m_flag.indice.push_back(i4);
            m_flag.indice.push_back(i5);
            m_flag.indice.push_back(i6);

            // Triangle 2 (face arrière)
            m_flag.indice.push_back(i6);
            m_flag.indice.push_back(i5);
            m_flag.indice.push_back(i7);
        }
    }
}
*/



uint32_t Tflag_med::_load_texture(std::string file)
{
	OSurface surface;
    uint32_t texture;

	surface.load(file);
	if (!surface.exist())
	{
		ECO_error_set("echec chargement %s >Tflag_med.load_texture", file.c_str());
		return 0;
	}

    //activation des textures
    glEnable(GL_TEXTURE_2D);
    //if (texture) glDeleteTextures(1,&texture);
    //generation de la texture
    glGenTextures(1, &texture);
    //selection de la texture
    glBindTexture(GL_TEXTURE_2D, texture);
	// génération
	glTexImage2D(GL_TEXTURE_2D, 0, surface.getFormat(), surface.getWidth(), surface.getHeight(), 0, surface.getFormat(), GL_UNSIGNED_BYTE, surface.getPixels() );
	// on applique les filtres usuels
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	//déverrouillage libération
    glBindTexture(GL_TEXTURE_2D, 0);

    return texture;
}





void Tflag_med::_make_vao()
{
    //génération de l'identifiant du VAO
    glGenVertexArrays(1, &m_flag.vao);
    //activation du VAO
    glBindVertexArray(m_flag.vao);
    // créations des buffers
    glGenBuffers(nbr_vao_buffer, m_flag.vao_buffer);

    // position

    // on recupere la position modifiée par le compute shader
    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_compute_buffer[0]); // vec4 !
    glEnableVertexAttribArray(LOCATION_POSITION);
    glVertexAttribPointer(
        LOCATION_POSITION,
        4,
        GL_FLOAT,
        GL_FALSE,
        sizeof(OVec4_f),
        (void*)0
    );

    // normal
/*
    // on recupere la normale modifiée par le compute shader
    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_compute_buffer[2]);
    glEnableVertexAttribArray(LOCATION_NORMAL);
    glVertexAttribPointer(
        LOCATION_NORMAL,
        3,
        GL_FLOAT,
        GL_FALSE,
        sizeof(OVec3_f),
        (void*)0
    );
*/
    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[1]);
    glBufferData(
        GL_ARRAY_BUFFER,
        sizeof(OVec3_f) * m_flag.normal.size(),
        m_flag.normal.data(),
        GL_STATIC_DRAW
    );

    glEnableVertexAttribArray(LOCATION_NORMAL);
    glVertexAttribPointer(
        LOCATION_NORMAL,
        3,
        GL_FLOAT,
        GL_FALSE,
        0,
        (void*)0
    );

    // uv

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[2]);
    glBufferData(
        GL_ARRAY_BUFFER,
        sizeof(OVec2_f) * m_flag.uv.size(),
        m_flag.uv.data(),
        GL_STATIC_DRAW
    );

    glEnableVertexAttribArray(LOCATION_TEXCOORD);
    glVertexAttribPointer(
        LOCATION_TEXCOORD,
        2,
        GL_FLOAT,
        GL_FALSE,
        0,
        (void*)0
    );

    // model

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[LOCATION_MODEL]);
    glBufferData(
        GL_ARRAY_BUFFER,
        sizeof(OMat4) * m_flag.model_multi.size(),
        m_flag.model_multi.data(),
        GL_DYNAMIC_DRAW
    );
    for (int i = 0; i < 4; ++i)
    {
        glEnableVertexAttribArray(LOCATION_MODEL + i);
        glVertexAttribPointer(
            LOCATION_MODEL + i,
            4,
            GL_FLOAT,
            GL_FALSE,
            sizeof(OMat4),
            (void*)(sizeof(float) * i * 4)
        );
        glVertexAttribDivisor(LOCATION_MODEL + i, 1);
    }

    // indices

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_flag.vao_buffer[7]);
    glBufferData(
        GL_ELEMENT_ARRAY_BUFFER,
        sizeof(unsigned) * m_flag.indice.size(),
        m_flag.indice.data(),
        GL_STATIC_DRAW
    );

    // ---

    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}


// necessite openGL 4.4 (glBufferStorage)
void Tflag_med::_make_vao_compute()
{

    glGenVertexArrays(1, &m_flag.vao_compute);
    glBindVertexArray(m_flag.vao_compute);

    glGenBuffers(nbr_vao_compute_buffer, m_flag.vao_compute_buffer);

    // ---

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_flag.vao_compute_buffer[0]);
    glBufferData(
        GL_SHADER_STORAGE_BUFFER,
        sizeof(OVec4_f) * m_flag.cpt_pos.size(),
        m_flag.cpt_pos.data(),
        GL_DYNAMIC_COPY
    );
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_flag.vao_compute_buffer[0]);


    glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_flag.vao_compute_buffer[1]);
    glBufferData(
        GL_SHADER_STORAGE_BUFFER,
        sizeof(OVec4_f) * m_flag.cpt_pos_prev.size(),
        m_flag.cpt_pos_prev.data(),
        GL_DYNAMIC_COPY
    );
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_flag.vao_compute_buffer[1]);

    /*
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_flag.vao_compute_buffer[2]);
    glBufferData(
        GL_SHADER_STORAGE_BUFFER,
        sizeof(OVec3_f) * m_flag.cpt_norm.size(),
        m_flag.cpt_norm.data(),
        GL_DYNAMIC_COPY
    );
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_flag.vao_compute_buffer[2]);
*/
    // ---

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}
/*
Les mots clés veulent dire :
    STATIC : peu ou pas modifié
    DYNAMIC : modifié souvent
    STREAM : modifié presque chaque frame
et :
    DRAW : écrit par CPU -> lu par GPU
    READ : écrit par GPU -> lu par CPU
    COPY : GPU -> GPU

Compute shader = GPU qui écrit
Vertex shader = GPU qui lit
Donc la majorité des buffers PBD sont GPU <-> GPU, pas CPU <-> GPU.
*/




void Tflag_med::_setModel(size_t idx)
{
    OMat4 model;

    model.loadIdentity();
    model.translate(m_pos[idx]);
    model.rotate(m_rot[idx].z, 0, 0, 1);
    model.rotate(m_rot[idx].y, 0, 1, 0);
    model.rotate(m_rot[idx].x, 1, 0, 0);
    model.scale(m_scale[idx]);

    m_model_multi[idx] = model;
    m_model_multi[idx].transpose(); // car ma matrice est horizontale

    mesh.setModelMulti(m_model_multi); // passe au vao du mesh

    // ---

    m_flag.model_multi[idx] = model;
    m_flag.model_multi[idx].translate(m_flag.decal_pos);
    //m_flag.model_multi[idx].rotate(90.0f,0,0,1);
    m_flag.model_multi[idx].transpose();

    // = setModelMulti
    glBindVertexArray(m_flag.vao);
    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[LOCATION_MODEL]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(OMat4) * m_flag.model_multi.size(), &m_flag.model_multi[0], GL_DYNAMIC_DRAW);
    //désactivation du VAO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}

void Tflag_med::setWind(float speed, float dir)
{
    dshFlag_cpt0.enable();
        dshFlag_cpt0.setWind(speed, dir);
    dshFlag_cpt0.disable();
}


// --------------
// --- shader ---
// --------------

// dshFlag
static const char* pVS = " \n\
#version 330 core \n\
 \n\
layout (location = 0) in vec4 in_vertex; // vec4 !  \n\
layout (location = 1) in vec3 in_normal; \n\
layout (location = 2) in vec2 in_uv; \n\
layout (location = 3) in mat4 in_model; \n\
// in_model = 3 4 5 6 \n\
 \n\
uniform mat4 u_vp; \n\
 \n\
out vec2 var_uv; \n\
out vec3 var_normal; \n\
out vec3 var_model; \n\
 \n\
// toujours multi instanced \n\
void main() \n\
{ \n\
    gl_Position = u_vp * in_model * vec4(in_vertex.xyz, 1.0); \n\
    //gl_Position = u_vp * in_model * in_vertex; \n\
    \n\
    var_uv = in_uv; \n\
    var_model = (in_model * vec4(in_vertex.xyz, 1.0)).xyz; \n\
    // gere le scale de in_model \n\
    mat3 normalMatrix = transpose(inverse(mat3(in_model))); \n\
    var_normal = normalize(normalMatrix * in_normal); \n\
    //var_normal = (in_model * vec4(in_normal, 0.0)).xyz; \n\
}";


static const char* pFS = " \n\
#version 330 core \n\
 \n\
in vec2 var_uv; \n\
in vec3 var_normal; \n\
in vec3 var_model; \n\
 \n\
layout (location = 0) out vec3 positionMap; \n\
layout (location = 1) out vec4 colorMap; \n\
layout (location = 2) out vec3 normalMap; \n\
 \n\
uniform sampler2D u_colorMap; \n\
 \n\
void main(){ \n\
    vec4 color = texture(u_colorMap, var_uv); \n\
	float alpha = smoothstep(0.6, 0.95, color.a); \n\
    if (alpha < 0.05) discard; \n\
    \n\
    positionMap = var_model; \n\
    color.a = 0.5; \n\
    colorMap = color; \n\
    normalMap = var_normal; \n\
}";



TDshFlag_med::TDshFlag_med()
{
	//
}

bool TDshFlag_med::create()
{
    if (!ODshader::init())
    {
        ECO_error_set("echec init TDshFlag_med");
        return false;
    }
    if (!addShader(GL_VERTEX_SHADER, pVS))
    {
        ECO_error_set("echec add vertex TDshFlag_med");
        return false;
    }
    if (!addShader(GL_FRAGMENT_SHADER, pFS))
    {
        ECO_error_set("echec add fragment TDshFlag_med");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize TDshFlag_med");
        return false;
    }

    m_vp = getUniformLocation("u_vp");
    m_colorMap = getUniformLocation("u_colorMap");
/*
    // phase de test
	if (m_vp == INVALID_UNIFORM_LOCATION
    || m_colorMap == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec TDshFlag_med");
        return false;
    }
*/
    return true;
}

void TDshFlag_med::setVP(const OMat4& vp)
{
    glUniformMatrix4fv(m_vp, 1, GL_TRUE, vp.getFv());
}

void TDshFlag_med::setTexture(uint32_t texture)
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(m_colorMap, 0);

    //glActiveTexture(GL_TEXTURE0);
}

// ---------------------------
// --- compute integration ---
// ---------------------------

//layout(std430, binding = 0) buffer Positions {
//  vec4 pos[];     > xyz = position, w = masse inverse (0 = point fixé)
//layout(std430, binding = 1) buffer PrevPositions {
//  vec4 prevPos[]; > w = 0.0 -> sommet fixe (hampe du drapeau)

static const char* pCS0 = " \n\
#version 450 \n\
 \n\
layout (local_size_x = 256) in; // Taille du workgroup \n\
 \n\
layout(std430, binding = 0) buffer Positions { vec4 pos[]; }; \n\
layout(std430, binding = 1) buffer PrevPositions { vec4 prevPos[]; }; \n\
 \n\
uniform uint    u_vertexCount; \n\
uniform float   u_wind_speed = 0.2; \n\
uniform vec3    u_wind_dir = vec3(0.5,0.5,0.0); \n\
 \n\
const float c_delta_time = 0.02; \n\
const vec3  c_gravity = vec3(0, 0, -9.81*0.1);    // gravité z et normalisée \n\
\n\
void main() { \n\
    uint idx = gl_GlobalInvocationID.x; \n\
    if (idx >= u_vertexCount-1) return; \n\
 \n\
    float invMass = pos[idx].w; \n\
    if (invMass == 0.0) return; // point fixe \n\
 \n\
    vec3 current = pos[idx].xyz; \n\
    vec3 previous = prevPos[idx].xyz; \n\
    vec3 velocity = current - previous; \n\
    vec3 accel = c_gravity + (u_wind_dir * u_wind_speed); \n\
 \n\
    vec3 next = current + velocity + accel * (c_delta_time * c_delta_time); \n\
    prevPos[idx].xyz = current; \n\
    pos[idx].xyz = next; \n\
}";


TDshFlag_med_cpt0::TDshFlag_med_cpt0()
{
    //
}

bool TDshFlag_med_cpt0::create()
{
    if (!ODshader::init())
    {
        ECO_error_set("echec init TDshFlag_med_cpt0");
        return false;
    }
    if (!addShader(GL_COMPUTE_SHADER, pCS0))
    {
        ECO_error_set("echec add fragment TDshFlag_med_cpt0");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize TDshFlag_med_cpt0");
        return false;
    }

    m_wind_dir = getUniformLocation("u_wind_dir");
    m_wind_speed = getUniformLocation("u_wind_speed");
    m_vertexCount = getUniformLocation("u_vertexCount");
/*
    // phase de test
	if (m_wind_speed == INVALID_UNIFORM_LOCATION
    || m_wind_dir == INVALID_UNIFORM_LOCATION
    || m_vertexCount == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec TDshFlag_med_cpt");
        return false;
    }
*/
    return true;
}

// 20 km/h & degré trigo
void TDshFlag_med_cpt0::setWind(float speed, float dir)
{
    float d = dir* ECO_DEGTORAD;
    glUniform3f(m_wind_dir, cos(d), sin(d), 0.0f);

    glUniform1f(m_wind_speed, speed * 0.05f);
}

void TDshFlag_med_cpt0::setColRow(uint32_t col, uint32_t row)
{
    glUniform1ui(m_vertexCount, col*row);
}


// ---------------------------
// --- compute contraintes ---
// ---------------------------

static const char* pCS1 = " \n\
#version 450 \n\
 \n\
layout(local_size_x = 256) in; \n\
 \n\
layout(std430, binding = 0) buffer Positions { vec4 pos[]; }; \n\
 \n\
uniform uint  u_cols; \n\
uniform uint  u_rows; \n\
uniform float u_restLength; \n\
 \n\
void satisfy(uint a, uint b) { \n\
    vec3 p1 = pos[a].xyz; \n\
    vec3 p2 = pos[b].xyz; \n\
 \n\
    float w1 = pos[a].w; \n\
    float w2 = pos[b].w; \n\
    float wsum = w1 + w2; \n\
    if (wsum == 0.0) return; \n\
 \n\
    vec3 delta = p2 - p1; \n\
    float dist = length(delta); \n\
    if (dist == 0.0) return; \n\
 \n\
    vec3 correction = delta * (1.0 - u_restLength / dist); \n\
 \n\
    pos[a].xyz += correction * (w1 / wsum); \n\
    pos[b].xyz -= correction * (w2 / wsum); \n\
} \n\
 \n\
void main() { \n\
    uint idx = gl_GlobalInvocationID.x; \n\
    uint x = idx % u_cols; \n\
    uint y = idx / u_cols; \n\
    if (x >= u_cols || y >= u_rows) return; \n\
 \n\
    if (x + 1 < u_cols) satisfy(idx, idx + 1); \n\
    if (y + 1 < u_rows) satisfy(idx, idx + u_cols); \n\
}";



TDshFlag_med_cpt1::TDshFlag_med_cpt1()
{
    //
}

bool TDshFlag_med_cpt1::create()
{
    if (!ODshader::init())
    {
        ECO_error_set("echec init TDshFlag_med_cpt1");
        return false;
    }
    if (!addShader(GL_COMPUTE_SHADER, pCS1))
    {
        ECO_error_set("echec add fragment TDshFlag_med_cpt1");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize TDshFlag_med_cpt1");
        return false;
    }

    m_col = getUniformLocation("u_cols");
    m_row = getUniformLocation("u_rows");
    m_restLength = getUniformLocation("u_restLength");

/*
    // phase de test
	if ( m_col == INVALID_UNIFORM_LOCATION
    || m_row == INVALID_UNIFORM_LOCATION
    || m_restLength == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec TDshFlag_med_cpt1");
        return false;
    }
*/
    return true;
}


void TDshFlag_med_cpt1::setColRow(uint32_t col, uint32_t row)
{
    glUniform1ui(m_col, col);
    glUniform1ui(m_row, row);
}

void TDshFlag_med_cpt1::setRestLength(float value)
{
    glUniform1f(m_restLength, value);
}


// ------------------------
// --- compute normales ---
// ------------------------

static const char* pCS2 = " \n\
#version 450 \n\
 \n\
layout(local_size_x = 256) in; \n\
layout(std430, binding = 0) buffer Positions { vec4 pos[]; }; \n\
layout(std430, binding = 2) buffer Normals { vec3 nor[]; }; \n\
 \n\
uniform uint u_cols; \n\
uniform uint u_rows; \n\
 \n\
uint idx(uint x, uint y) \n\
{ \n\
    return x + y * u_cols; \n\
} \n\
 \n\
void main() \n\
{ \n\
    uint id = gl_GlobalInvocationID.x; \n\
    uint x = id % u_cols; \n\
    uint y = id / u_cols; \n\
    if (x >= u_cols || y >= u_rows) return; \n\
 \n\
    vec3 n = vec3(0.0); \n\
    vec3 p = pos[idx(x,y)].xyz; \n\
    // triangle bas-droite \n\
    if (x + 1 < u_cols && y + 1 < u_rows) \n\
    { \n\
        vec3 p1 = pos[idx(x+1,y)].xyz; \n\
        vec3 p2 = pos[idx(x,y+1)].xyz; \n\
        n += cross(p1 - p, p2 - p); \n\
    } \n\
    // triangle haut-gauche \n\
    if (x > 0 && y > 0) \n\
    { \n\
        vec3 p1 = pos[idx(x,y-1)].xyz; \n\
        vec3 p2 = pos[idx(x-1,y)].xyz; \n\
        n += cross(p1 - p, p2 - p); \n\
    } \n\
    // triangle haut-droite \n\
    if (x + 1 < u_cols && y > 0) \n\
    { \n\
        vec3 p1 = pos[idx(x+1,y-1)].xyz; \n\
        vec3 p2 = pos[idx(x,y-1)].xyz; \n\
        n += cross(p1 - p, p2 - p); \n\
    } \n\
    // triangle bas-gauche \n\
    if (x > 0 && y + 1 < u_rows) \n\
    { \n\
        vec3 p1 = pos[idx(x,y+1)].xyz; \n\
        vec3 p2 = pos[idx(x-1,y)].xyz; \n\
        n += cross(p1 - p, p2 - p); \n\
    } \n\
 \n\
    // nor[id] = vec4(normalize(n), 0.0); // si vec4  \n\
    nor[id] = normalize(n); \n\
}";


TDshFlag_med_norm::TDshFlag_med_norm()
{
    //
}

bool TDshFlag_med_norm::create()
{
    if (!ODshader::init())
    {
        ECO_error_set("echec init TDshFlag_med_norm");
        return false;
    }
    if (!addShader(GL_COMPUTE_SHADER, pCS2))
    {
        ECO_error_set("echec add fragment TDshFlag_med_norm");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize TDshFlag_med_norm");
        return false;
    }

    m_col = getUniformLocation("u_cols");
    m_row = getUniformLocation("u_rows");

/*
    // phase de test
	if ( m_col == INVALID_UNIFORM_LOCATION
    || m_row == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec TDshFlag_med_norm");
        return false;
    }
*/
    return true;
}


void TDshFlag_med_norm::setColRow(uint32_t col, uint32_t row)
{
    glUniform1ui(m_col, col);
    glUniform1ui(m_row, row);
}



