
#include "OGrass3.h"

// shader en fin

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

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

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

OGrass3::~OGrass3()
{
    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]);


}

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

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

    // ---

    const std::vector<Blade> blades = generate_blades();

    // ---
/*
    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 >OGrass3.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();
}


std::vector<Blade> generate_blades()
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<float> orientation_dis(0, glm::pi<float>());
    std::uniform_real_distribution<float> dis(-1, 1);

    std::vector<Blade> blades;
    // Generate grass blades using jittered stratified sampling
    for (int i = -200; i < 200; ++i)
    {
        for (int j = -200; j < 200; ++j)
        {
            const float x = static_cast<float>(i) / 10 - 1 + dis(gen) * 0.1f;
            const float y = static_cast<float>(j) / 10 - 1 + dis(gen) * 0.1f;

            const float blade_height = glm::simplex(glm::vec2(x, y)) * 0.5f + 0.7f;

            blades.emplace_back(
                OVec4_f(x, 0, y, orientation_dis(gen)),
                OVec4_f(x, blade_height, y, blade_height),
                OVec4_f(x, blade_height, y, 0.1f),
                OVec4_f(0, blade_height, 0, 0.7f + dis(gen) * 0.3f));
        }
    }
    return blades;
}


void OGrass3::draw(OMat4& vp, OVec3_f& eyePos, float ambient)
{
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    glBindVertexArray(m_vao);
    shGrass.enable();
    glDrawArraysIndirect(GL_PATCHES, reinterpret_cast<void*>(0));
}


void OGrass3::update()
{
    shGrass_cpt.enable();
    shGrass_cpt.setTime(static_cast<float>(glfwGetTime()), delta_time.count() / 1e3f);
    shGrass_cpt.setWind(wind_magnitude, wind_wave_length, wind_wave_period);
/*
    shGrass_cpt.setTime("current_time", static_cast<float>(glfwGetTime()));
    shGrass_cpt.setFloat("delta_time", delta_time.count() / 1e3f);
    shGrass_cpt.setFloat("wind_magnitude", wind_magnitude);
    shGrass_cpt.setFloat("wind_wave_length", wind_wave_length);
    shGrass_cpt.setFloat("wind_wave_period", wind_wave_period);

*/
    glDispatchCompute(blades_count_, 1, 1);
}




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

	surface.load(file);
	if (!surface.exist())
	{
		ECO_error_set("echec chargement %s >OGrass3.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 OGrass3::_make_vao()
{
    blades_count_ = static_cast<GLuint>(blades.size());

    glPatchParameteri(GL_PATCH_VERTICES, 1);

    glGenVertexArrays(1, &m_vao);
    glBindVertexArray(m_vao);

    unsigned int grass_input_buffer = 0;
    glGenBuffers(1, &grass_input_buffer);

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, grass_input_buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast<GLsizei>(blades.size() * sizeof(Blade)), blades.data(), GL_DYNAMIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, grass_input_buffer);

    unsigned int grass_output_buffer = 0;
    glGenBuffers(1, &grass_output_buffer);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, grass_output_buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast<GLsizei>(blades.size() * sizeof(Blade)), nullptr, GL_STREAM_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, grass_output_buffer);

    NumBlades numBlades;
    unsigned int grass_indirect_buffer = 0;
    glGenBuffers(1, &grass_indirect_buffer);
    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, grass_indirect_buffer);
    glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(NumBlades), &numBlades, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, grass_output_buffer);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, grass_indirect_buffer);

    // v0 attribute
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Blade), reinterpret_cast<void*>(offsetof(Blade, v0)));
    glEnableVertexAttribArray(0);

    // v1 attribute
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Blade), reinterpret_cast<void*>(offsetof(Blade, v1)));
    glEnableVertexAttribArray(1);

    // v2 attribute
    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Blade), reinterpret_cast<void*>(offsetof(Blade, v2)));
    glEnableVertexAttribArray(2);

    // dir attribute
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(Blade), reinterpret_cast<void*>(offsetof(Blade, up)));
    glEnableVertexAttribArray(3);

    shGrass_cpt = ShaderBuilder{}.load("grass.comp.glsl", Shader::Type::Compute).build();
    shGrass_cpt.use();

    shGrass = ShaderBuilder{}
                      .load("grass.vert.glsl", Shader::Type::Vertex)
                      .load("grass.tesc.glsl", Shader::Type::TessControl)
                      .load("grass.tese.glsl", Shader::Type::TessEval)
                      .load("grass.frag.glsl", Shader::Type::Fragment)
                      .build();
}


// grass donnees/image/ fichier.bin / herbe_0.png 0.1 / herbe_1.png 0.1 / null 0 /
void OGrass3::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 450 \n\
 \n\
layout(location = 0) in vec4 v0; \n\
layout(location = 1) in vec4 v1; \n\
layout(location = 2) in vec4 v2; \n\
layout(location = 3) in vec4 up; \n\
 \n\
out VS_OUT \n\
{ \n\
  vec4 v1; \n\
  vec4 v2; \n\
  vec4 up; \n\
  vec4 dir; \n\
} vs_out; \n\
 \n\
void main() { \n\
  vs_out.v1 = v1; \n\
  vs_out.v2 = v2; \n\
  vs_out.up = vec4(normalize(up.xyz), up.w); \n\
 \n\
  float angle = v0.w; \n\
  vec3 dir = normalize(cross(vs_out.up.xyz, vec3(sin(angle), 0, cos(angle)))); \n\
  vs_out.dir = vec4(dir, 0.0); \n\
  gl_Position = v0; \n\
}";

static const char* pTC = " \n\
#version 450 \n\
 \n\
layout(binding = 0) uniform CameraBufferObject { \n\
    mat4 view; \n\
    mat4 proj; \n\
    vec3 position; \n\
} camera; \n\
 \n\
in VS_OUT \n\
{ \n\
  vec4 v1; \n\
  vec4 v2; \n\
  vec4 up; \n\
  vec4 dir; \n\
} tesc_in[]; \n\
 \n\
patch out TESC_OUT \n\
{ \n\
  vec4 v1; \n\
  vec4 v2; \n\
  vec4 up; \n\
  vec4 dir; \n\
} tesc_out; \n\
 \n\
layout(vertices = 1) out; \n\
 \n\
void main() { \n\
  gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; \n\
 \n\
  tesc_out.v1 = tesc_in[gl_InvocationID].v1; \n\
  tesc_out.v2 = tesc_in[gl_InvocationID].v2; \n\
  tesc_out.up = tesc_in[gl_InvocationID].up; \n\
  tesc_out.dir = tesc_in[gl_InvocationID].dir; \n\
 \n\
  float z1 = (camera.proj * camera.view * vec4(tesc_out.v1.xyz, 1)).z; \n\
  float z2 = (camera.proj * camera.view * vec4(tesc_out.v2.xyz, 1)).z; \n\
 \n\
  if (z1 < 0.8 && z2 < 0.8) { \n\
    gl_TessLevelInner[0] = 2.0; \n\
    gl_TessLevelInner[1] = 7.0; \n\
    gl_TessLevelOuter[0] = 7.0; \n\
    gl_TessLevelOuter[1] = 2.0; \n\
    gl_TessLevelOuter[2] = 7.0; \n\
    gl_TessLevelOuter[3] = 2.0; \n\
  } else { \n\
    gl_TessLevelInner[0] = 1.0; \n\
    gl_TessLevelInner[1] = 3.0; \n\
    gl_TessLevelOuter[0] = 3.0; \n\
    gl_TessLevelOuter[1] = 1.0; \n\
    gl_TessLevelOuter[2] = 3.0; \n\
    gl_TessLevelOuter[3] = 1.0; \n\
  } \n\
}";

static const char* pTE = " \n\
#version 450 \n\
 \n\
layout(quads, equal_spacing, ccw) in; \n\
 \n\
layout(binding = 0) uniform CameraBufferObject { \n\
    mat4 view; \n\
    mat4 proj; \n\
    vec3 position; \n\
} camera; \n\
 \n\
patch in TESC_OUT \n\
  vec4 v1; \n\
  vec4 v2; \n\
  vec4 up; \n\
  vec4 dir; \n\
} tese_in; \n\
 \n\
out TESE_OUT \n\
{ \n\
  vec3 position; \n\
  vec3 normal; \n\
  vec2 uv; \n\
} tese_out; \n\
 \n\
void main() { \n\
  const float u = gl_TessCoord.x; \n\
  const float v = gl_TessCoord.y; \n\
 \n\
  const vec3 v0 = gl_in[0].gl_Position.xyz; \n\
  const vec3 v1 = tese_in.v1.xyz; \n\
  const vec3 v2 = tese_in.v2.xyz; \n\
  const vec3 dir = tese_in.dir.xyz; \n\
  const float width = tese_in.v2.w; \n\
 \n\
  const vec3 a = v0 + v * (v1 - v0); \n\
  const vec3 b = v1 + v * (v2 - v1); \n\
  const vec3 c = a + v * (b - a); \n\
  const vec3 t1 = dir; \n\
 \n\
  const vec3 c0 = c - t1 * width * 0.5; \n\
  const vec3 c1 = c + t1 * width * 0.5; \n\
  const vec3 t0 = normalize(b - a); \n\
 \n\
  const float t = u + 0.5 * v - u * v; \n\
  const vec3 p = (1.0 - t) * c0 + t * c1; \n\
 \n\
  tese_out.position = p; \n\
  tese_out.uv = vec2(u, v); \n\
  tese_out.normal = normalize(cross(t0, t1)); \n\
  gl_Position = camera.proj * camera.view * vec4(p, 1.0); \n\
}";

static const char* pFS = " \n\
#version 450 \n\
 \n\
layout(binding = 0) uniform CameraBufferObject { \n\
    mat4 view; \n\
    mat4 proj; \n\
    vec3 position; \n\
} camera; \n\
 \n\
in TESE_OUT \n\
{ \n\
  vec3 position; \n\
  vec3 normal; \n\
  vec2 uv; \n\
} frag_in; \n\
 \n\
layout(location = 0) out vec4 out_color; \n\
 \n\
void main() { \n\
  vec2 uv = frag_in.uv; \n\
  vec3 normal = normalize(frag_in.normal); \n\
 \n\
  vec3 upperColor = vec3(0.4,1,0.1); \n\
  vec3 lowerColor = vec3(0.0,0.2,0.1); \n\
 \n\
  vec3 sunLightDirection = normalize(vec3(1.0, 1.0, 1.0)); \n\
 \n\
  vec3 viewPos = camera.position; \n\
  vec3 FragPos = frag_in.position; \n\
  vec3 viewDir = normalize(viewPos - FragPos); \n\
  vec3 reflectDir = reflect(-sunLightDirection, normal); \n\
  vec3 halfwayDir = normalize(sunLightDirection + viewDir); \n\
 \n\
  // 0: bottom, 1: top \n\
  float bottomToTop = uv.y; \n\
  vec3 mixedColor = mix(lowerColor, upperColor, bottomToTop); \n\
 \n\
  // ambient \n\
  float ambientStrength = 0.4; \n\
  vec3 ambient = ambientStrength * mixedColor; \n\
 \n\
  // diffuse \n\
  float diff = clamp(dot(normal, sunLightDirection), 0.5, 1.0) * 0.4; \n\
  vec3 diffuse = diff * mixedColor; \n\
 \n\
  // specular \n\
  float specularStrength = 0.3; \n\
  float shininess = 4.0; \n\
  float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess); \n\
  vec3 specular = specularStrength * vec3(spec); \n\
 \n\
  vec3 result = ambient + diffuse + specular; \n\
 \n\
  out_color = vec4(result, 1.0); \n\
}";

OShGrass3::OShGrass3()
{
	m_vp = 0; m_time = 0; m_eyePos = 0;
	m_ambient = 0;
	m_grassMap = 0;
}

bool OShGrass3::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 OShGrass3::setVP(const OMat4& vp)
{
    glUniformMatrix4fv(m_vp, 1, GL_TRUE, vp.getFv());
}

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

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

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

    //glActiveTexture(GL_TEXTURE0);
}

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

// --- compute shader ---

static const char* pVS = " \n\
#version 450 \n\
 \n\
#define WORKGROUP_SIZE 32 \n\
layout(local_size_x = WORKGROUP_SIZE, \n\
local_size_y = 1, \n\
local_size_z = 1) in; \n\
 \n\
layout(binding = 0) uniform CameraBufferObject { \n\
    mat4 view; \n\
    mat4 proj; \n\
    vec3 position; \n\
} camera; \n\
 \n\
uniform float uTime; \n\
uniform float uDelta_time; \n\
 \n\
uniform float uWind_magnitude; \n\
uniform float uWind_wave_length; \n\
uniform float uWind_wave_period; \n\
 \n\
struct Blade { \n\
    vec4 v0; \n\
    vec4 v1; \n\
    vec4 v2; \n\
    vec4 up; \n\
}; \n\
 \n\
layout(binding = 1, std140) buffer inputBuffer { Blade inputBlades[]; }; \n\
layout(binding = 2, std140) buffer outputBuffer { Blade outputBlades[]; }; \n\
// Indirect drawing count \n\
layout(binding = 3) buffer NumBlades { \n\
    uint vertexCount; \n\
    uint instanceCount;// = 1 \n\
    uint firstVertex;// = 0 \n\
    uint firstInstance;// = 0 \n\
} numBlades; \n\
 \n\
bool inBounds(float value, float bounds) { \n\
    return (value >= -bounds) && (value <= bounds); \n\
} \n\
 \n\
float rand(float seed) { \n\
    return fract(sin(seed)*100000.0); \n\
} \n\
 \n\
void main() { \n\
    // Reset the number of blades to 0 \n\
    if (gl_GlobalInvocationID.x == 0) { \n\
        numBlades.vertexCount = 0; \n\
    } \n\
    barrier();// Wait till all threads reach this point \n\
 \n\
    uint index = gl_GlobalInvocationID.x; \n\
    vec3 v0 = inputBlades[index].v0.xyz; \n\
    vec3 v1 = inputBlades[index].v1.xyz; \n\
    vec3 v2 = inputBlades[index].v2.xyz; \n\
    vec3 up = normalize(inputBlades[index].up.xyz); \n\
    float orientation = inputBlades[index].v0.w; \n\
    float height = inputBlades[index].v1.w; \n\
    float width = inputBlades[index].v2.w; \n\
    float stiffness = inputBlades[index].up.w; \n\
 \n\
    // Frustum culling \n\
    vec4 v0ClipSpace = camera.proj * camera.view * vec4(v0, 1); \n\
    vec4 v1ClipSpace = camera.proj * camera.view * vec4(v1, 1); \n\
    v0ClipSpace /= v0ClipSpace.w; \n\
    v1ClipSpace /= v1ClipSpace.w; \n\
 \n\
    bool v0OutFrustum =  v0ClipSpace.x < -1 || v0ClipSpace.x > 1 || v0ClipSpace.y < -1 || v0ClipSpace.y > 1; \n\
    bool v1OutFrustum = v1ClipSpace.x < -1 || v1ClipSpace.x > 1 || v1ClipSpace.y < -1 || v1ClipSpace.y > 1; \n\
    if (v0OutFrustum && v1OutFrustum) return; \n\
 \n\
    // Distance culling \n\
    const float far1 = 0.98; \n\
    if (v0ClipSpace.z > far1 && v1ClipSpace.z > far1 && rand(index) > 0.7) { \n\
        return; \n\
    } \n\
    const float far2 = 0.99; \n\
    if (v0ClipSpace.z > far2 && v1ClipSpace.z > far2 && rand(index) > 0.3) { \n\
        return; \n\
    } \n\
    const float far3 = 0.995; \n\
    if (v0ClipSpace.z > far3 && v1ClipSpace.z > far3 && rand(index) > 0.2) { \n\
        return; \n\
    } \n\
 \n\
    // Apply forces { \n\
    //  Gravities \n\
    vec3 gE = vec3(0, -0.98, 0); \n\
    vec3 widthDir = vec3(cos(orientation), 0, sin(orientation)); \n\
    vec3 bladeFace = normalize(cross(up, widthDir)); \n\
    vec3 gF = 0.25*length(gE)*bladeFace; \n\
    vec3 g = gE + gF; \n\
 \n\
    //  Recovery \n\
    vec3 r = (v0 + up * height - v2) * stiffness; \n\
 \n\
    //  Wind \n\
    vec3 windForce = 0.25 * uWind_magnitude * \n\
    vec3( \n\
    sin(uTime * 3. / uWind_wave_period + v0.x * 0.1 * 11 / uWind_wave_length), \n\
    0, \n\
    sin(uTime * 3. / uWind_wave_period + v0.z * 0.2 * 11 / uWind_wave_length) * 0.1 \n\
    ); \n\
    float fd = 1 - abs(dot(normalize(windForce), normalize(v2 - v0))); \n\
    float fr = dot((v2 - v0), up) / height; \n\
    vec3 w = windForce * fd * fr; \n\
 \n\
    v2 += (0.1 * g + r + w) * uDelta_time; \n\
 \n\
    float lproj = length(v2 - v0 - up * dot((v2-v0), up)); \n\
    v1 = v0 + height*up*max(1-lproj/height, 0.05*max(lproj/height, 1)); \n\
 \n\
    inputBlades[index].v1.xyz = v1; \n\
    inputBlades[index].v2.xyz = v2; \n\
    // } \n\
 \n\
    outputBlades[atomicAdd(numBlades.vertexCount, 1)] = inputBlades[index]; \n\
}";


OShGrass3_cpt::OShGrass3_cpt()
{
	m_time = 0; m_delta_time = 0;
	m_wind_mag = 0;
	m_wind_wav = 0;
	m_wind_per = 0;
}

bool OShGrass3_cpt::create()
{
    if (!OShader::init())
    {
        ECO_error_set("echec init > OShGrass_cpt");
        return false;
    }
    if (!addShader(GL_COMPUTE_SHADER, pVS))
    {
        ECO_error_set("echec pCp > OShGrass_cpt");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize > OShGrass_cpt");
        return false;
    }

    m_time = getUniformLocation("uTime");
    m_delta_time = getUniformLocation("uDelta_time");
    m_wind_mag = getUniformLocation("uWind_magnitude");
    m_wind_wav = getUniformLocation("uWind_wave_length");
    m_wind_per = getUniformLocation("uWind_wave_periode");

    if (m_time == INVALID_UNIFORM_LOCATION
    || m_deltatime == INVALID_UNIFORM_LOCATION
    || m_wind_mag == INVALID_UNIFORM_LOCATION
    || m_wind_wav == INVALID_UNIFORM_LOCATION
    || m_wind_per == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec OShGrass_cpt");
        return false;
    }

    return true;
}

void OShGrass3::setTime(float time, float delta_time);
{
    glUniform1f(m_time, time);
    glUniform1f(m_delta_time, delta_time);
}

void OShGrass3::setWind(float magnitude, float wave, float period)
{
    glUniform1f(m_wind_mag, magnitude);
    glUniform1f(m_wind_wav, wave);
    glUniform1f(m_wind_per, period);
}




// ---

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

bool OGrass3_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.detection_sol(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;
}


