
#include "Tgrass_geo.h"

// shader en fin
// Tgrass_geo_make en fin

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

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

    for (int i = 0; i < m_nbr_vao; i++)
    {
        m_vao[i] = 0;
        m_vbo[i] = 0;
    }
}

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

Tgrass_geo::~Tgrass_geo()
{
    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 (int i = 0; i < m_nbr_vao; i++)
    {
        glDeleteBuffers(1, &m_vbo[i]);
        glDeleteVertexArrays(1, &m_vao[i]);
    }
}

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

// grass donnees/image/ fichier.bin / herbe_0.png 0.1 / herbe_1.png 0.1 / null 0 /
void Tgrass_geo::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_geo::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_geo::_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]);

    // ---

    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_geo.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];
    }

    _make_vao();
}

void Tgrass_geo::_make_vao()
{
    for (int i = 0; i < 3; i++)
    {
        if (m_vbo[i]) glDeleteBuffers(1, &m_vbo[i]);
        if (m_vao[i]) glDeleteVertexArrays(1, &m_vao[i]);

        //génération de l'identifiant du VAO
        glGenVertexArrays(1, &m_vao[i]);
        glBindVertexArray(m_vao[i]);

        // vertex buffer
        glGenBuffers(1, &m_vbo[i]);
        glBindBuffer(GL_ARRAY_BUFFER, m_vbo[i]);
        glBufferData(GL_ARRAY_BUFFER, m_pos[i].size() * sizeof(OVec4_f), &m_pos[i][0], GL_STATIC_DRAW);

        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(OVec4_f), 0);
        glEnableVertexAttribArray(0);
    }

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






void Tgrass_geo::draw(OMat4& vp, OVec3_f& eyePos, float ambient)
{
    static float time = 0.0f;
    //if (!m_visible) return;

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

    glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDisable(GL_CULL_FACE);

    time += 0.05f; // 1.0f/50.0f * ? / vitesse de mvt

	shGrass.enable();
		shGrass.setVP(vp);
		shGrass.setEyePos(eyePos);
        shGrass.setAmbient(ambient);
        shGrass.setTime(time);
        // >
		shGrass.setTexture(m_tex_grass[0]);
		//
		glBindVertexArray(m_vao[0]);
        glDrawArrays(GL_POINTS, 0, m_pos[0].size());
        // >
        if (m_pos[1].size() > 0)
        {
            shGrass.setTexture(m_tex_grass[1]);
            //
            glBindVertexArray(m_vao[1]);
            glDrawArrays(GL_POINTS, 0, m_pos[1].size());
        }
        // >
        if (m_pos[2].size() > 0)
        {
            shGrass.setTexture(m_tex_grass[2]);
            //
            glBindVertexArray(m_vao[2]);
            glDrawArrays(GL_POINTS, 0, m_pos[2].size());
        }

    //désactive
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}



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

	surface.load(file);
	if (!surface.exist())
	{
		ECO_error_set("echec chargement %s >Tgrass_geo.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;
}



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

    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 herbe ---
// --------------------


static const char* pVS = " \n\
#version 330 core   \n\
 \n\
layout (location = 0) in vec4 in_vertex; \n\
 \n\
out float var_size; \n\
 \n\
void main() \n\
{ \n\
	gl_Position =  vec4(in_vertex.xyz, 1.0); \n\
	var_size = in_vertex.w; \n\
}";


static const char* pGS = " \n\
#version 330 core   \n\
 \n\
layout(points) in; \n\
layout (triangle_strip, max_vertices = 12) out; \n\
 \n\
in float var_size[1]; \n\
 \n\
out vec2 var_uv;\n\
out float var_colorVariation;\n\
\n\
uniform mat4 uVP;\n\
uniform vec3 uEyePos;\n\
uniform float uTime; \n\
//uniform float uWindDir = 10.0; \n\
//uniform float uWindSpeed = 20.0; \n\
uniform float uLod1 = 15.0f;\n\
uniform float uLod2 = 30.0f;\n\
uniform float uLod3 = 50.0f;\n\
 \n\
const float csin[3] = {0.0,  0.5,  0.5}; \n\
const float ccos[3] = {1.0,  0.5, -0.5}; \n\
 \n\
float hash(vec2 p) \n\
{ \n\
    return fract(sin(dot(p ,vec2(127.1,311.7))) * 43758.5453); \n\
    // return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453123);\n\
} \n\
 \n\
void main()\n\
{\n\
    vec3 pos = gl_in[0].gl_Position.xyz; \n\
    vec3 p2; \n\
    float halfsize = var_size[0]; \n\
    float rnd = hash(pos.xy * 0.5); \n\
    vec2 winddir; \n\
 \n\
     // au loin, rarefie l'herbe \n\
    int lod; \n\
	float t = 6.0f; \n\
	float dist = length(pos - uEyePos); \n\
	if (dist > uLod2) t *= 1.5f;\n\
	dist += (rnd * t) - (t / 2.0f);\n\
    if (dist < uLod1) lod = 3; else if (dist < uLod2) lod = 2; else if (dist < uLod3) lod = 1; else return; \n\
 \n\
    var_colorVariation = 1.0-rnd; \n\
 \n\
    for (int i = 0; i < lod; i++) \n\
    { \n\
        // animation \n\
        float bendFactor = sin(uTime * 1.5 + rnd * 10.0 + float(i) * 1.7) * 0.05; \n\
        winddir.x += bendFactor * ccos[i]; \n\
        winddir.y += bendFactor * csin[i]; \n\
 \n\
        p2.x = pos.x + (-ccos[i] * halfsize); \n\
        p2.y = pos.y + (-csin[i] * halfsize); \n\
        p2.z = pos.z; \n\
        gl_Position = uVP * vec4(p2, 1.0); \n\
        var_uv = vec2(0.0,0.0); \n\
        EmitVertex(); \n\
 \n\
        p2.x += winddir.x; \n\
        p2.y += winddir.y; \n\
        p2.z += halfsize; \n\
        gl_Position = uVP * vec4(p2, 1.0); \n\
        var_uv = vec2(0.0,1.0); \n\
        EmitVertex(); \n\
 \n\
        p2.x = pos.x + (ccos[i] * halfsize); \n\
        p2.y = pos.y + (csin[i] * halfsize); \n\
        p2.z -= halfsize; \n\
        gl_Position = uVP * vec4(p2, 1.0); \n\
        var_uv = vec2(1.0,0.0); \n\
        EmitVertex(); \n\
 \n\
        p2.x += winddir.x; \n\
        p2.y += winddir.y; \n\
        p2.z += halfsize; \n\
        gl_Position = uVP * vec4(p2, 1.0); \n\
        var_uv = vec2(1.0,1.0); \n\
        EmitVertex(); \n\
        \n\
        EndPrimitive(); \n\
    } \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\
    // === fwidth-based LOD === \n\
    //float pixelSize = length(fwidth(var_uv)); \n\
    //float lod = clamp(log2(1.0/pixelSize*128), 0.0, 4.0); \n\
    //out_color = textureLod(uGrassMap, var_uv, lod); \n\
	out_color = texture(uGrassMap, var_uv); // on genere mipmap \n\
	float alpha = smoothstep(0.6, 0.95, out_color.a); \n\
    if (alpha < 0.05) discard; \n\
    out_color.a = alpha; \n\
    vec3 variationTint = mix(vec3(0.8, 1.0, 0.8), vec3(1.0, 0.9, 0.7), var_colorVariation);  \n\
    out_color.rgb *= variationTint; \n\
    out_color.rgb = out_color.rgb; \n\
    out_color.rgb *= uAmbient; \n\
}";




Tgrass_geo_shader::Tgrass_geo_shader()
{
	//
}

bool Tgrass_geo_shader::create()
{
    if (!OShader::init())
    {
        ECO_error_set("Echec Tgrass_geo_shader > init");
        return false;
    }
    if (!addShader(GL_VERTEX_SHADER, pVS))
    {
        ECO_error_set("Echec Tgrass_geo_shader > pVS");
        return false;
    }
    if (!addShader(GL_GEOMETRY_SHADER, pGS))
    {
        ECO_error_set("Echec Tgrass_geo_shader > pGS");
        return false;
    }
    if (!addShader(GL_FRAGMENT_SHADER, pFS))
    {
        ECO_error_set("Echec Tgrass_geo_shader > pFS");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("Echec Tgrass_geo_shader > finalize");
        return false;
    }

    m_vp = getUniformLocation("uVP");
    m_eyePos = getUniformLocation("uEyePos");
    m_grassMap = getUniformLocation("uGrassMap");
    m_ambient = getUniformLocation("uAmbient");
    m_time = getUniformLocation("uTime");
    //m_windDir = getUniformLocation("uWindDir");
    //m_windSpeed = getUniformLocation("uWindSpeed");
    m_lod1 = getUniformLocation("uLod1");
    m_lod2 = getUniformLocation("uLod2");
    m_lod3 = getUniformLocation("uLod3");
/*
    if (m_vp == INVALID_UNIFORM_LOCATION
    || m_eyePos == INVALID_UNIFORM_LOCATION
    || m_grassMap == INVALID_UNIFORM_LOCATION
    || m_ambient == INVALID_UNIFORM_LOCATION
    || m_time == INVALID_UNIFORM_LOCATION
    || m_windDir == INVALID_UNIFORM_LOCATION
    || m_windSpeed == INVALID_UNIFORM_LOCATION
    || m_lod1 == INVALID_UNIFORM_LOCATION
    || m_lod2 == INVALID_UNIFORM_LOCATION
    || m_lod3 == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec Tgrass_geo_shader");
        return false;
    }
*/
    return true;
}

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

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

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

void Tgrass_geo_shader::setLod(float val1, float val2, float val3)
{
    glUniform1f(m_lod1, val1);
    glUniform1f(m_lod2, val2);
    glUniform1f(m_lod3, val3);
}

void Tgrass_geo_shader::setTexture(uint32_t texGrass)
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texGrass);
    glUniform1i(m_grassMap, 0);
}

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

void Tgrass_geo_shader::setWind(float dir, float speed)
{
    //glUniform1f(m_windDir, dir);    // 0 360
    //glUniform1f(m_windSpeed, speed);    // 0  0.04
}



// ---

// 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_geo_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_geo_make(filepng, filecol, decalage, echelle, densite);
}

bool Tgrass_geo_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 >Tgrass_geo_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 >Tgrass_geo_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;
}
