
#include "Tgrass_ins.h"

// shader en fin

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

Tgrass_ins::Tgrass_ins()
{
    m_tex_grass[0] = 0;
    m_tex_grass[1] = 0;
    m_tex_grass[2] = 0;
}

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

Tgrass_ins::~Tgrass_ins()
{
    if (m_tex_grass[0] > 0) glDeleteTextures(1, &m_tex_grass[0]);
    if (m_tex_grass[1] > 0) glDeleteTextures(1, &m_tex_grass[1]);
    if (m_tex_grass[2] > 0) glDeleteTextures(1, &m_tex_grass[2]);

    for (size_t i = 0; i < m_nbr_vao; i++)
    {
        if (m_vao_buffer[i][0] != 0) glDeleteBuffers(m_nbr_vao_buffer, m_vao_buffer[i]);
        if (m_vao[i] != 0) glDeleteVertexArrays(1, &m_vao[i]);
        m_vao[i] = 0;
    }
}

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

// grass donnees/image/ fichier.bin / herbe_0.png 0.1 / herbe_1.png 0.1 / null 0 /
void Tgrass_ins::create(std::string flux)
{
    std::vector<std::string> strList;

    ECO_strCut(strList, flux, ' ');

    m_code = strList[0];
    m_path = strList[1];
    m_file = strList[2];
    // 3
    m_str_grass[0] = strList[4];
    m_scale[0] = ECO_strToFloat(strList[5]);
    // 6
    m_str_grass[1] = strList[7];
    m_scale[1] = ECO_strToFloat(strList[8]);
    // 9
    m_str_grass[2] = strList[10];
    m_scale[2] = ECO_strToFloat(strList[11]);

    _composition();

}

void Tgrass_ins::create(std::ifstream& flux)
{
    std::string sep;

    flux >> m_code >> m_path >> m_file >> sep;
    flux >> m_str_grass[0] >> m_scale[0] >> sep;
    flux >> m_str_grass[1] >> m_scale[1] >> sep;
    flux >> m_str_grass[2] >> m_scale[2] >> sep;

    _composition();

}

void Tgrass_ins::_composition()
{
    shGrass.create();

    // ---

    m_tex_grass[0] = _load_texture(m_path + m_str_grass[0]);
    if (m_str_grass[1] != "null") m_tex_grass[1] = _load_texture(m_path + m_str_grass[1]);
    if (m_str_grass[2] != "null") m_tex_grass[2] = _load_texture(m_path + m_str_grass[2]);

    // ---

    // posé au sol /  face à l'est
    float w = -0.5f, h = 0.5f;

    m_vertex[0].set(0, -w, 0);
    m_vertex[1].set(0,  w, 0);
    m_vertex[2].set(0, -w, h);
    //
    m_vertex[3].set(0,  w, 0);
    m_vertex[4].set(0, -w, h);
    m_vertex[5].set(0,  w, h);
    //
    m_vertex[6].set(0, -w, h);
    m_vertex[7].set(0,  w, h);
    m_vertex[8].set(0, -w, h*2);
    //
    m_vertex[9].set(0,  w, h);
    m_vertex[10].set(0, -w, h*2);
    m_vertex[11].set(0,  w, h*2);
    //
    m_uv[0].set(0.0f, 0.0f);
    m_uv[1].set(1.0f, 0.0f);
    m_uv[2].set(0.0f, 0.5f);
    //
    m_uv[3].set(1.0f, 0.0f);
    m_uv[4].set(0.0f, 0.5f);
    m_uv[5].set(1.0f, 0.5f);
    //
    m_uv[6].set(0.0f, 0.5f);
    m_uv[7].set(1.0f, 0.5f);
    m_uv[8].set(0.0f, 1.0f);
    //
    m_uv[9].set(1.0f, 0.5f);
    m_uv[10].set(0.0f, 1.0f);
    m_uv[11].set(1.0f, 1.0f);

    // ---

    std::string fichier;
    size_t nb;
    // #include <stdio.h>
    FILE *fichier_bin;


    fichier = m_path + m_file;
    if (! ECO_fileExist(fichier))
    {
        ECO_error_set("fichier non trouvé %s >Tgrass_ins.create", fichier.c_str());
        return;
    }

    fichier_bin = fopen(fichier.c_str() , "rb");
    m_pos.resize( 3, std::vector<OVec4_f>(0) );

    // red
    fread( &nb , sizeof(size_t) , 1 , fichier_bin);
    m_pos[0].resize(nb);

    for (size_t i = 0; i < nb; i++)
    {
        fread( &m_pos[0][i] , sizeof(float), 4 , fichier_bin);
        m_pos[0][i].w *= m_scale[0];
    }

    // green
    fread( &nb , sizeof(size_t) , 1 , fichier_bin);
    m_pos[1].resize(nb);

    for (size_t i = 0; i < nb; i++)
    {
        fread( &m_pos[1][i] , sizeof(float), 4 , fichier_bin);
        m_pos[1][i].w *= m_scale[1];
    }

    // blue
    fread( &nb , sizeof(size_t) , 1 , fichier_bin);
    m_pos[2].resize(nb);

    for (size_t i = 0; i < nb; i++)
    {
        fread( &m_pos[2][i] , sizeof(float), 4 , fichier_bin);
        m_pos[2][i].w *= m_scale[2];
    }

    // ---

    for (size_t idx = 0; idx < m_nbr_vao; idx++)
    {
        m_grass[idx].clear();
        m_grass[idx].reserve(m_pos[idx].size());

        for (size_t i = 0; i < m_pos[idx].size(); i++)
        {
            m_grass[idx].push_back(s_grass());
            m_grass[idx][i].position.set( m_pos[idx][i].x, m_pos[idx][i].y, m_pos[idx][i].z);
            m_grass[idx][i].rotation = ECO_rnd_f(0.0f, 2.0f); // 0 > 2 pi
            m_grass[idx][i].scale = m_pos[idx][i].w;
            m_grass[idx][i].windPhase = ECO_rnd_f(6.28318f);
        }
    }

    _make_vao();
}



void Tgrass_ins::draw(OMat4& vp, OVec3_f& eyePos, float ambient)
{
    static float time = 0.0f;

    if (m_pos[0].size() == 0) return;

    time += 0.05f; // 1/50 fps;

    glDisable(GL_CULL_FACE);

    // rappeller les effets de transparence
    //glEnable(GL_DEPTH_TEST);
    //glEnable(GL_TEXTURE_2D);
    //glEnable(GL_BLEND);
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    shGrass.enable();
        shGrass.setVP(vp);
        shGrass.setTime(time);
        shGrass.setEyePos(eyePos);
        shGrass.setAmbient(ambient);
        shGrass.setTexture(m_tex_grass[0]);

        glBindVertexArray(m_vao[0]);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 12, m_grass[0].size());

        if (m_pos[1].size() > 0)
        {
            shGrass.setTexture(m_tex_grass[1]);
            //
            glBindVertexArray(m_vao[1]);
            glDrawArraysInstanced(GL_TRIANGLES, 0, 12, m_grass[1].size());
        }
        if (m_pos[2].size() > 0)
        {
            shGrass.setTexture(m_tex_grass[2]);
            //
            glBindVertexArray(m_vao[2]);
            glDrawArraysInstanced(GL_TRIANGLES, 0, 12, m_grass[2].size());
        }

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}


uint32_t Tgrass_ins::_load_texture(std::string file)
{
	OSurface surface;
	uint32_t texture = 0;

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

    //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
	int w = surface.getWidth();
	int h = surface.getHeight();
	glTexImage2D(GL_TEXTURE_2D, 0, surface.getFormat(), w, h, 0, surface.getFormat(), GL_UNSIGNED_BYTE, surface.getPixels() );

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);

        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4);
        glGenerateMipmap(GL_TEXTURE_2D);

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

    return texture;
}

void Tgrass_ins::_make_vao()
{
    for (int idx = 0; idx < m_nbr_vao; idx++)
    {
        //génération de l'identifiant du VAO
        glGenVertexArrays(1, &m_vao[idx]);
        //activation du VAO
        glBindVertexArray(m_vao[idx]);
        // créations des buffers
        glGenBuffers(m_nbr_vao_buffer, m_vao_buffer[idx]);

        glBindBuffer(GL_ARRAY_BUFFER, m_vao_buffer[idx][0]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(m_vertex), m_vertex, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

        glBindBuffer(GL_ARRAY_BUFFER, m_vao_buffer[idx][1]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(m_uv), m_uv, GL_STATIC_DRAW);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);

        // Buffer d’instances
        glBindBuffer(GL_ARRAY_BUFFER, m_vao_buffer[idx][2]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(s_grass) * m_grass[idx].size(), m_grass[idx].data(), GL_STATIC_DRAW);

        // Instance attributes
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(s_grass), (void*)offsetof(s_grass, position));
        glVertexAttribDivisor(2, 1);

        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(s_grass), (void*)offsetof(s_grass, rotation));
        glVertexAttribDivisor(3, 1);

        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(s_grass), (void*)offsetof(s_grass, scale));
        glVertexAttribDivisor(4, 1);

        glEnableVertexAttribArray(5);
        glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(s_grass), (void*)offsetof(s_grass, windPhase));
        glVertexAttribDivisor(5, 1);
    }

    // fin
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}


// grass donnees/image/ fichier.bin / herbe_0.png 0.1 / herbe_1.png 0.1 / null 0 /
void Tgrass_ins::save(std::ofstream& flux)
{
    flux << FLUX_GRASS2 << " ";

    flux << m_code << " " << m_path << " " << m_file << " / ";
    flux << m_str_grass[0] << " " << m_scale[0] << " / ";
    flux << m_str_grass[1] << " " << m_scale[1] << " / ";
    flux << m_str_grass[2] << " " << m_scale[2] << " /" << std::endl;
}




// --- shader ---

static const char* pVS = " \n\
#version 330 core   \n\
 \n\
layout (location = 0) in vec3 in_vertex; \n\
layout (location = 1) in vec2 in_uv; \n\
layout (location = 2) in vec3 instancePos; \n\
layout (location = 3) in float instanceRot; \n\
layout (location = 4) in float instanceScale; \n\
layout (location = 5) in float instanceWindPhase; // random par instance \n\
 \n\
out vec2 var_uv; \n\
out float var_colorVariation; \n\
 \n\
uniform mat4 uVP; \n\
uniform float uTime; \n\
uniform vec3 uEyePos; \n\
uniform float uWindSpeed = 20.0; \n\
uniform float uLod = 30.0;\n\
 \n\
float hash(vec2 st) { \n\
    return fract(sin(dot(st ,vec2(127.1,311.7))) * 43758.5453); \n\
    //return mod((st.x*st.x)+(0.13-st.x*st.y), 0.01)/0.01; \n\
} \n\
 \n\
void main() \n\
{ \n\
    vec3 pos = in_vertex; \n\
    pos *= instanceScale; \n\
 \n\
    // lod brutal \n\
    float dist = distance(instancePos, uEyePos); \n\
    //if (dist > uLod*5) { gl_Position = uVP * vec4(0.0); return; }; \n\
    // lod progressif \n\
    if (dist > uLod * ((hash(instancePos.xy)*10))) { gl_Position = uVP * vec4(0.0); return; }; \n\
    // rotation sur verticale z \n\
    // simple et rapide \n\
    float rotation = instanceRot; \n\
    // selon vue camera > plus lourd \n\
    //vec3 toCamera = normalize(uEyePos - instancePos); \n\
    //vec3 up = vec3(0.0, 0.0, 1.0); \n\
    //vec3 right = cross(toCamera, up); \n\
    //float rotation = atan(right.x, right.y); \n\
    float s = sin(rotation);\n\
    float c = cos(rotation);\n\
    mat2 rot = mat2(c, -s, s, c);\n\
    pos.xy = rot * pos.xy;\n\
    // wind \n\
    float swayAmount = uWindSpeed * 0.05 * pos.z; \n\
    float sway = cos(uTime + instanceWindPhase) * swayAmount; \n\
    pos.x += sway * (pos.z*0.2); \n\
    pos.y += sway * 0.2; \n\
 \n\
    pos += instancePos; \n\
    gl_Position = uVP * vec4(pos, 1.0); \n\
 \n\
    var_uv = in_uv; \n\
    var_colorVariation = 1.2 - hash(instancePos.xy)*0.5; \n\
}";



static const char* pFS = " \n\
#version 330 core \n\
 \n\
in vec2 var_uv; \n\
in float var_colorVariation; \n\
 \n\
uniform sampler2D uGrassMap; \n\
uniform float uAmbient = 0.8; \n\
 \n\
out vec4 out_color; \n\
 \n\
void main() \n\
{ \n\
	out_color = texture(uGrassMap, var_uv); \n\
    float alpha = smoothstep(0.6, 0.95, out_color.a); \n\
    if (alpha < 0.05) discard; \n\
    //float alpha = out_color.a; \n\
    //if (alpha < 0.9) discard; \n\
    out_color.rgb *= var_colorVariation * uAmbient; \n\
    out_color.a = alpha; \n\
}";

Tgrass_ins_shader::Tgrass_ins_shader()
{
	//
}

bool Tgrass_ins_shader::create()
{
    if (!OShader::init())
    {
        ECO_error_set("echec init > OShGrass");
        return false;
    }
    if (!addShader(GL_VERTEX_SHADER, pVS))
    {
        ECO_error_set("echec pVS > OShGrass");
        return false;
    }
    if (!addShader(GL_FRAGMENT_SHADER, pFS))
    {
        ECO_error_set("echec pFS > OShGrass");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize > OShGrass");
        return false;
    }

    m_vp = getUniformLocation("uVP");
    m_time = getUniformLocation("uTime");
    m_ambient = getUniformLocation("uAmbient");
    m_grassMap = getUniformLocation("uGrassMap");
    m_eyePos = getUniformLocation("uEyePos");

/*
    if (m_vp == INVALID_UNIFORM_LOCATION
    || m_time == INVALID_UNIFORM_LOCATION
    || m_grassMap == INVALID_UNIFORM_LOCATION
    || m_noiseMap == INVALID_UNIFORM_LOCATION
    || m_alphaMap == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec OShGrass");
        return false;
    }
*/
    return true;
}



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

void Tgrass_ins_shader::setAmbient(float value)
{
    glUniform1f(m_ambient, value);
}

void Tgrass_ins_shader::setEyePos(const OVec3_f& pos)
{
    glUniform3f(m_eyePos, pos.x, pos.y, pos.z);
}

void Tgrass_ins_shader::setTexture(uint32_t grass)
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, grass);
   	glUniform1i(m_grassMap, 0);

    //glActiveTexture(GL_TEXTURE0);
}

// +1
void Tgrass_ins_shader::setTime(float value)
{
    glUniform1f(m_time, value);
}




// ---

// donnees/image/herbe_ne.png donnees/mesh/zone/zone_ne.obj / decalage pos.xyz / echelle densite /
// image png du masque (RVB) / mesh obj (calé en 0,0,0 ; avec texture 0->1) / position finale / densité de l'herbe
bool Tgrass_ins_make(std::string flux)
{
    std::vector<std::string> strList;

    ECO_strCut(strList, flux, ' ');

    std::string filepng = strList[0];
    std::string filecol = strList[1];
    // 2
    OVec3_f decalage(ECO_strToFloat(strList[3]), ECO_strToFloat(strList[4]), ECO_strToFloat(strList[5]));
    // 6
    float echelle = ECO_strToFloat(strList[7]);
    float densite = ECO_strToFloat(strList[8]);
    // 9

    return Tgrass_ins_make(filepng, filecol, decalage, echelle, densite);
}

bool Tgrass_ins_make(std::string filepng, std::string filecol, OVec3_f decalage, float taille, float densite)
{
    OSurface surface;
    uint8_t R,G,B,A;
    int width, height;
    float ratioW, ratioH;
    std::vector< std::vector<OVec4_f> > pos; // position size + 3 couleurs

    float color;
    //
    OCollision collision;
    OVec3_f col;
    OVec3_f line_start, line_end;
    size_t idx, mrq;

    // ---

    OVec3_f origine;
    OVec3_f bound_min, bound_max;
    float carte_w, carte_h;
    OMesh_obj mesh;

    OMesh_obj::P_setPath("");

    if (!mesh.create(filecol))
    {
        ECO_error_set("echec lecture du fichier collision %s >OGrass1_make", filecol.c_str());
        return false;
    }

    origine = mesh.getBound_min();
    bound_min = mesh.getBound_min();
    bound_max = mesh.getBound_max();
    carte_w = bound_max.x - bound_min.x;
    carte_h = bound_max.y - bound_min.y;
    //ECO_error_set("%s > %f %f", filecol.c_str(), carte_w, carte_h);

    collision.create(filecol);

    // ---

    if (!surface.load(filepng))
    {
        ECO_error_set("echec chargement image %s >OGrass1_make", filepng.c_str());
        return false;
    }

    width = surface.getWidth();
    height = surface.getHeight();
    ratioW = carte_w / (float)width;
    ratioH = carte_h / (float)height;
    //ECO_error_set("%s > %f %f", filecol.c_str(), ratioW, ratioH);

    // ---

    // les trois couleurs
    pos.resize( 3, std::vector<OVec4_f>(0) );
    float d2 = densite *2.0f;

    // on calcule les véritables positions
    // l'image a été inversée verticalement au chargement (mode normal de openGL)
    // surface.flipV();
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            surface.getPixel_RGBA(x,y, &R, &G, &B, &A);

            // le fond doit etre noir (a=1), mais au cas où
            //if (A == 0)
            //{
            //    continue;
            //}
            //else
            if (R > 0)
            {
                idx = 0;
                color = R;
            }
            else if (G > 0)
            {
                idx = 1;
                color = G;
            }
            else if (B > 0)
            {
                idx = 2;
                color = B;
            }
            else
                continue;

            mrq = pos[idx].size();
            pos[idx].push_back( OVec4_f() );
            //
            pos[idx][mrq].x = origine.x + (x * ratioW) + (ECO_rnd_f(d2) - densite);
            pos[idx][mrq].y = origine.y + (y * ratioH) + (ECO_rnd_f(d2) - densite);
            pos[idx][mrq].z = origine.z;
            pos[idx][mrq].w = ((color / 255.0f) * taille) + 0.1f;

            // trouve le sol
            col.set(pos[idx][mrq].x, pos[idx][mrq].y, pos[idx][mrq].z);
            collision.detect_ground(col, 100.0f);
            pos[idx][mrq].z = col.z;
            //ECO_error_set("%f %f %f", col.x, col.y, col.z);
            //ECO_error_set("%f", col.z);
        }
    }

    // ---
    // --- sauver en binaire
    // ---

    // #include <stdio.h>
    FILE *fichier_bin;
    size_t nbr;
    OVec4_f decal;
    size_t ext = filepng.find(".");
    std::string filebin = filepng.substr(0, ext) + ".bin";

    fichier_bin = fopen(filebin.c_str() , "wb");

    for (int i = 0; i < 3; i++)
    {
        nbr = pos[i].size();
        fwrite( &nbr, sizeof(size_t), 1, fichier_bin);

        for (size_t p = 0; p < pos[i].size(); p++)
        {
            decal = pos[i][p];
            decal.x += decalage.x;
            decal.y += decalage.y;
            decal.z += decalage.z;
            //decal.w = size
            fwrite( &decal , sizeof(float), 4 , fichier_bin);
        }
    }

    return true;
}


