
#include "Tmeca.h"

static OTextureList textureList; //conteneur texture commun à tous les meshs
static int          p_fps = 50;
static float        p_bound_factor = 0.8f;

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

Tmeca::Tmeca()
{
	m_frame_end = 0;
}

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

Tmeca::~Tmeca()
{
    //
}


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

// les methodes <anim> en fin


void Tmeca::_clear_model()
{
    for (size_t i = 0; i < partie.size(); i++)
    {
        partie[i].axePos = partie[i].decalPos;
        //partie[i].axePos.x += partie[i].px[0];
        //partie[i].axePos.y += partie[i].py[0];
        //partie[i].axePos.z += partie[i].pz[0];

        partie[i].axeRot = partie[i].decalRot;
        //partie[i].axeRot.x += partie[i].rx[0];
        //partie[i].axeRot.y += partie[i].ry[0];
        //partie[i].axeRot.z += partie[i].rz[0];

        //partie[i].axeScale.x = partie[i].sx[0];
        //partie[i].axeScale.y = partie[i].sy[0];
        //partie[i].axeScale.z = partie[i].sz[0];
    }

    _setModel();
}

void Tmeca::create(std::string flux)
{
    std::vector<std::string> strList;
    std::string sep;

    strList = ECO_strCut(flux, ' ');

    m_code = strList[0];
    m_path = strList[1];
    m_file = strList[2];
    m_cull_face = ECO_strToInt( strList[3] );
    // 4
    m_pos.x = ECO_strToFloat(strList[5]);
    m_pos.y = ECO_strToFloat(strList[6]);
    m_pos.z = ECO_strToFloat(strList[7]);
    m_rot.x = ECO_strToFloat(strList[8]);
    m_rot.y = ECO_strToFloat(strList[9]);
    m_rot.z = ECO_strToFloat(strList[10]);
    // 11
    m_state  = ECO_strToInt(strList[12]);
    m_action = strList[13]; // ex:  coff_00/open/ouvrir (code+action+animation)
    // 14

    _composition();
}

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

    flux >> m_code >> m_path >> m_file >> m_cull_face >> sep;
    flux >> m_pos.x >> m_pos.y >> m_pos.z;
    flux >> m_rot.x >> m_rot.y >> m_rot.z >> sep;
    //
    flux >> m_state >> m_action >> sep;

    _composition();
}

bool Tmeca::_composition()
{
    std::string path, file;

    m_scale.set(1,1,1);
    m_select = false;
    m_visible = true;

    // ---

    std::vector<std::string> strList;

    // mec port_00 meca/ porte_00.txt 1 / -3 26 0 0 0 -90 / 2 port_00/open/ouvrir+chem_01_a1/state/1+coff_00/state/1
    strList.clear();
    ECO_strCut(strList, m_action, '+');

    m_action_lst.clear();
    for (size_t i = 0; i < strList.size(); i++)
    {
        m_action_lst.push_back(strList[i]);
    }

    // ---

    partie.clear();
    lstAnimation.clear();
    lstAction.clear();
    lstCycle.clear();

    m_etat_animation = MEC_ANIM_NULL;
    m_frame = 0;
    m_frame_end = 0;
    m_loop = 0;
    m_state_waiting = -1;
    m_action_waiting = MEC_ACT_NULL;

    path = PATH_MESH + m_path;
    file = path + m_file;

    std::ifstream flux;
    flux.open( file.c_str(), std::ios::in);
    if (!flux)
    {
        ECO_error_set("echec chargement : %s >Tmeca.composition", file.c_str());
        return false;
    }
    //codage utf-8
	ECO_fileSeekBOM(flux);

	_load_animation(flux);
	_load_material(flux);
	_load_mesh(flux);

	_vao();

    // ---

    OVec3_f bmin, bmax;

    m_bound_min.set(10000,10000,10000);
    m_bound_max.set(-10000,-10000,-10000);

    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            bmin.set(10000,10000,10000);
            bmax.set(-10000,-10000,-10000);

            s_obj_part& ref = partie[i_par].meshPart[i_mes];

            for (size_t v = 0; v < ref.vertex.size(); v++)
            {
                if (ref.vertex[v].x < bmin.x) bmin.x = ref.vertex[v].x;
                if (ref.vertex[v].y < bmin.y) bmin.y = ref.vertex[v].y;
                if (ref.vertex[v].z < bmin.z) bmin.z = ref.vertex[v].z;

                if (ref.vertex[v].x > bmax.x) bmax.x = ref.vertex[v].x;
                if (ref.vertex[v].y > bmax.y) bmax.y = ref.vertex[v].y;
                if (ref.vertex[v].z > bmax.z) bmax.z = ref.vertex[v].z;
            }

            // p_bound_factor > la selection se fait naturellement sur le centre de l'objet
            bmin *= p_bound_factor;
            bmax *= p_bound_factor;
            ref.bound_min = bmin;
            ref.bound_max = bmax;

            if (bmin.x < m_bound_min.x) m_bound_min.x = bmin.x;
            if (bmin.y < m_bound_min.y) m_bound_min.y = bmin.y;
            if (bmin.z < m_bound_min.z) m_bound_min.z = bmin.z;

            if (bmax.x > m_bound_max.x) m_bound_max.x = bmax.x;
            if (bmax.y > m_bound_max.y) m_bound_max.y = bmax.y;
            if (bmax.z > m_bound_max.z) m_bound_max.z = bmax.z;
        }
    }

    //ECO_error_set("%s", m_file.c_str());
    //ECO_error_set("%s %d", m_file.c_str(), partie.size());
    //ECO_error_set("bmin %f %f %f", m_bound_min.x, m_bound_min.y, m_bound_min.z);
    //ECO_error_set("bmax %f %f %f", m_bound_max.x, m_bound_max.y, m_bound_max.z);


    // --- emissive

    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        partie[i_par].emissive.resize( partie[i_par].meshPart.size() );

        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            partie[i_par].emissive[i_mes].material_ref = partie[i_par].meshPart[i_mes].material;
            // ???
            partie[i_par].emissive[i_mes].color = material[ partie[i_par].meshPart[i_mes].material ].emissive;
        }
    }

	// --- attache des axes

	for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        for (size_t ref = 0; ref < partie.size(); ref++)
        {
            if (partie[ref].str_axeRef == partie[i_par].nom)
            {
                partie[ref].axeRef = i_par;
            }
        }
    }

	// --- position initiale

    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        partie[i_par].axePos = partie[i_par].decalPos;
        partie[i_par].axeRot = partie[i_par].decalRot;
        partie[i_par].axeScale.set(1.0f, 1.0f, 1.0f);
    }

    _setModel();

    return true;
}





void Tmeca::draw(ODshGeo& dshGeo)
{
    if (!m_visible) return;

    if (m_cull_face)
        glEnable(GL_CULL_FACE);
    else
        glDisable(GL_CULL_FACE);

    dshGeo.enable();

    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        dshGeo.setModel(partie[i_par].model);

        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            uint32_t dif = material[ partie[i_par].meshPart[i_mes].material ].tex_diffuse;
            uint32_t nor = material[ partie[i_par].meshPart[i_mes].material ].tex_normal;
            uint32_t spe = material[ partie[i_par].meshPart[i_mes].material ].tex_specular;
            dshGeo.setTextures(dif, nor, spe) ;

            //
            glBindVertexArray( partie[i_par].meshPart[i_mes].vao);

            uint32_t cpt = 0;

            switch ( partie[i_par].meshPart[i_mes].topologie_style)
            {
            case TOP_TRIANGLE:
                glDrawElements(GL_TRIANGLES, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0); //(void*)(sizeof(uint32_t)) );
                break;
            case TOP_QUAD:
                glDrawElements(GL_QUADS, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0);
                break;
            default:
                // nombre de faces
                for (size_t f = 0; f < partie[i_par].meshPart[i_mes].topologie.size(); f++)
                {
                    // m_topologie[f] = points/sommets par face
                    glDrawElements(GL_POLYGON,  partie[i_par].meshPart[i_mes].topologie[f], GL_UNSIGNED_INT, (void*)(sizeof(uint32_t) * cpt) );
                    cpt +=  partie[i_par].meshPart[i_mes].topologie[f];
                }
                break;
            }
        }
    }

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

    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0);
}


void Tmeca::draw(ODshGeo& dshGeo, ODshColor& dshColor)
{
    if (!m_visible) return;

    // --- selection ---

    if (m_select)
    {
        dshColor.enable();
            dshColor.setColorMul(1.0f, 0.5f, 1.0f); // multiplication du gris en fuschia / magenta

        if (m_cull_face)
            glEnable(GL_CULL_FACE);
        else
            glDisable(GL_CULL_FACE);

        for (size_t i_par = 0; i_par < partie.size(); i_par++)
        {
            dshColor.setModel(partie[i_par].model);

            for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
            {
                uint32_t dif = material[ partie[i_par].meshPart[i_mes].material ].tex_diffuse;
                uint32_t nor = material[ partie[i_par].meshPart[i_mes].material ].tex_normal;
                uint32_t spe = material[ partie[i_par].meshPart[i_mes].material ].tex_specular;
                dshColor.setTextures(dif, nor, spe) ;

                //
                glBindVertexArray( partie[i_par].meshPart[i_mes].vao);

                uint32_t cpt = 0;

                switch ( partie[i_par].meshPart[i_mes].topologie_style)
                {
                case TOP_TRIANGLE:
                    glDrawElements(GL_TRIANGLES, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0); //(void*)(sizeof(uint32_t)) );
                    break;
                case TOP_QUAD:
                    glDrawElements(GL_QUADS, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0);
                    break;
                default:
                    // nombre de faces
                    for (size_t f = 0; f < partie[i_par].meshPart[i_mes].topologie.size(); f++)
                    {
                        // m_topologie[f] = points/sommets par face
                        glDrawElements(GL_POLYGON,  partie[i_par].meshPart[i_mes].topologie[f], GL_UNSIGNED_INT, (void*)(sizeof(uint32_t) * cpt) );
                        cpt +=  partie[i_par].meshPart[i_mes].topologie[f];
                    }
                    break;
                }
            }
        }

        dshColor.setColorMul(1.0f, 1.0f, 1.0f);

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

        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE0);

        return;
    }

    // --- affichage normal ---

    if (m_cull_face)
        glEnable(GL_CULL_FACE);
    else
        glDisable(GL_CULL_FACE);

    dshGeo.enable();

    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        dshGeo.setModel(partie[i_par].model);

        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            uint32_t dif = material[ partie[i_par].meshPart[i_mes].material ].tex_diffuse;
            uint32_t nor = material[ partie[i_par].meshPart[i_mes].material ].tex_normal;
            uint32_t spe = material[ partie[i_par].meshPart[i_mes].material ].tex_specular;
            dshGeo.setTextures(dif, nor, spe) ;

            //
            glBindVertexArray( partie[i_par].meshPart[i_mes].vao);

            uint32_t cpt = 0;

            switch ( partie[i_par].meshPart[i_mes].topologie_style)
            {
            case TOP_TRIANGLE:
                glDrawElements(GL_TRIANGLES, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0); //(void*)(sizeof(uint32_t)) );
                break;
            case TOP_QUAD:
                glDrawElements(GL_QUADS, partie[i_par].meshPart[i_mes].indice.size(), GL_UNSIGNED_INT, 0);
                break;
            default:
                // nombre de faces
                for (size_t f = 0; f < partie[i_par].meshPart[i_mes].topologie.size(); f++)
                {
                    // m_topologie[f] = points/sommets par face
                    glDrawElements(GL_POLYGON,  partie[i_par].meshPart[i_mes].topologie[f], GL_UNSIGNED_INT, (void*)(sizeof(uint32_t) * cpt) );
                    cpt +=  partie[i_par].meshPart[i_mes].topologie[f];
                }
                break;
            }
        }
    }

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

    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0);

}





// la liste complete > port_00/open/ouvrir+chem_01_a1/state/1+coff_00/state/1
std::string Tmeca::getAction()
{
    return m_action;
}

// port_00/open/ouvrir par exemple
std::string Tmeca::getAction_sub(size_t idx)
{
    if (idx >= m_action_lst.size()) return "null";

    return m_action_lst[idx];
}

int Tmeca::getAction_waiting()
{
    return m_action_waiting;
}

// retourne l'index de l'action (open light ...) dans la liste
int Tmeca::getAction_exist(std::string ref)
{
    for (size_t i = 0; i < m_action_lst.size(); i++)
    {
        if (m_action_lst[i].find(ref) != std::string::npos) return i;
    }
    return -1;
}

// retourne l'index de l'animation, ou animation exist (> -1)
int Tmeca::getAnim(std::string commande)
{
    for (size_t i = 0; i < lstAnimation.size(); i++)
    {
        if (lstAnimation[i].find(commande) != std::string::npos)
        {
            return i;
        }
    }

    return -1;
}

// appelée une seule fois !
bool Tmeca::getAnim_end()
{
    bool result = (m_etat_animation == MEC_ANIM_END);

    m_etat_animation = MEC_ANIM_NULL;

    return result;
}

bool Tmeca::getAnim_cycle_end()
{
    return m_cycle.loop == 0;
}

int Tmeca::getAnim_state()
{
    return m_etat_animation;
}

size_t Tmeca::getAnimation_size()
{
    return lstAnimation.size();
}

std::string Tmeca::getAnimation_str(size_t idx)
{
    return lstAnimation[idx];
}

OVec3_f& Tmeca::getBound_min()
{
    return m_bound_min;
}

OVec3_f& Tmeca::getBound_max()
{
    return m_bound_max;
}

OVec3_f Tmeca::getBound_min(size_t part)
{
    // m_scale peut modifier temporairement (animation) le meca
    return partie[part].bound_min * m_scale;
}

OVec3_f Tmeca::getBound_max(size_t part)
{
    return partie[part].bound_max * m_scale;
}

std::string Tmeca::getCode()
{
    return m_code;
}

std::string Tmeca::getFile()
{
    return m_file;
}

void Tmeca::getIn(OVec3_f& srcpos)
{
    detector.getIn(srcpos);
}

std::string Tmeca::getName(size_t part)
{
    return partie[part].nom;
}

OMat4& Tmeca::getModel(size_t part)
{
    return partie[part].model;
}
/*
OMat4 Tmeca::getModel(const OMat4& model, size_t part)
{
    OMat4 s_model;

    // repere de base
    s_model = model;
    // scale général
    s_model.scale(m_scale);
    // decalage de la partie
    s_model.translate(partie[part].axePos);
    s_model.rotate(partie[part].axeRot.z, 0,0,1);
    s_model.rotate(partie[part].axeRot.y, 0,1,0);
    s_model.rotate(partie[part].axeRot.x, 1,0,0);
    s_model.scale(partie[part].axeScale);

    return s_model;
}
*/

size_t Tmeca::getNbPart()
{
    return partie.size();
}

OVec3_f& Tmeca::getPos()
{
    return m_pos;
}

OVec3_f& Tmeca::getRot()
{
    return m_rot;
}

OVec3_f& Tmeca::getScale()
{
    return m_scale;
}

bool Tmeca::getSelect()
{
    return m_select;
}

int Tmeca::getState()
{
    return m_state;
}

int Tmeca::getState_waiting()
{
    return m_state_waiting;
}

OVec3_f& Tmeca::getSubPos(size_t part)
{
    return partie[part].axePos;
}

OVec3_f& Tmeca::getSubRot(size_t part)
{
    return partie[part].axeRot;
}

bool Tmeca::getVisible()
{
    return m_visible;
}





bool Tmeca::_load_animation(std::ifstream& flux)
{
    std::string code, sep, str;
    size_t idx_load, idx;

	while(_load_readLine(flux))
    {
        if (!(flux >> code)) return false;

        //ECO_error_set("code anim %s", code.c_str());
        if (code == "#")
        {
            continue;
        }
        else if (code == "end")
        {
            break;
        }

        idx_load = 0;
        for (size_t i = 0; i < ECO_sizeOf(gl_code_ani); i++)
        {
            if (gl_code_ani[i] == code)
            {
                idx_load = i;
                break;
            }
        }

        switch(idx_load)
        {
        case MEC_ANI_OBJ:
            // obj nom_code objet3d / partie_reference decalpx py pz rx ry rz
            idx = partie.size();
            partie.push_back(s_partie());

            flux >> partie[idx].nom >> sep;
            flux >> partie[idx].str_axeRef;
            flux >> partie[idx].decalPos.x >> partie[idx].decalPos.y >> partie[idx].decalPos.z;
            flux >> partie[idx].decalRot.x >> partie[idx].decalRot.y >> partie[idx].decalRot.z;
            // le mesh-objet sera chargé dans load_mesh
            //ECO_error_set("partie + %s", partie[idx].nom.c_str());
            break;
        case MEC_ANI_DET:
            // code path / pos.xyz radius / state action / soundFile loop /
            std::getline(flux, str);
            str = ECO_trim(str);

            if (str != "null")
            {
                detector.create(str);
                detector.setPos(m_pos);
            }
            break;
        case MEC_ANI_ANI:
            // ani nom-ref code_action+loop son+loop / ...
            // ani descendre d00 0 donnees/mesh/bloc_4x4/descente.wav 0 /
            std::getline(flux, str);
            str = ECO_trim(str);

            lstAnimation.push_back( ECO_trim(str) );
            break;
        case MEC_ANI_ACT:
            // une animation agit sur plusieurs axes !
            // code_action (axe) pos_debut pos_fin x temps (seconde)
            // act m00 bloc pz -5 0 x 3.0 /
            std::getline(flux, str);
            str = ECO_trim(str);
            lstAction.push_back( ECO_trim(str) );
            break;
        case MEC_ANI_CYC:
            // nom-ref loop / action...
            // cyc alternance 99 / descendre monter /
            std::getline(flux, str);
            str = ECO_trim(str);
            lstCycle.push_back( ECO_trim(str) );
            break;
        }
	}

	return true;
}


bool Tmeca::_load_material(std::ifstream& flux)
{
    std::string code, name;
	size_t refMaterial = 0;

    while( _load_readLine(flux) )
	{
        if (!(flux >> code)) return false; //erreur;
        //ECO_error_set(code.c_str());

		if (code == "#")
        {
            continue; // remarque
        }
		else if (code == "end")
        {
            break;
        }
		else if (code == "newmtl")
		{
            std::string fileMaterial;

            flux >> fileMaterial;
            fileMaterial = ECO_trim(fileMaterial);

            refMaterial = material.size();
            material.push_back(s_obj_material());
            material[refMaterial].name = fileMaterial;
		}
		else if ( code == "Kd") //diffuse
		{
            flux >> material[refMaterial].diffuse.r >> material[refMaterial].diffuse.g >> material[refMaterial].diffuse.b;
		}
		else if ( code == "Ks")  //specular
		{
            flux >> material[refMaterial].specular.r >> material[refMaterial].specular.g >> material[refMaterial].specular.b;
		}
        else if ( code == "Ke")  //emissive
		{
		    flux >> material[refMaterial].emissive.r >> material[refMaterial].emissive.g >> material[refMaterial].emissive.b;
		}
		else if ( code == "d")   //transparent
        {
            float d;
		    flux >> d;
		    material[refMaterial].opacity = d;
        }
		else if (code == "map_Kd")
		{
            std::string str;
            //flux >> str;
            std::getline(flux, str); //il faut lire le reste de la ligne avec les paramètres
            str = ECO_trim(str);
            //
            material[refMaterial].map_diffuse = str;
		}
		// mtl note map_Bump et blender map_normal
        else if (code == "map_normal" || code == "map_Bump")
		{
            std::string str;
            //flux >> str;
            std::getline(flux, str); //il faut lire le reste de la ligne avec les paramètres
            str = ECO_trim(str);
            //
            material[refMaterial].map_normal = str;
		}
        else if (code == "map_Ks")
		{
            std::string str;
            //flux >> str;
            std::getline(flux, str); //il faut lire le reste de la ligne avec les paramètres
            str = ECO_trim(str);
            //
            material[refMaterial].map_specular = str;
		}
        else if (code == "map_Ke")
		{
            std::string str;
            //flux >> str;
            std::getline(flux, str); //il faut lire le reste de la ligne avec les paramètres
            str = ECO_trim(str);
            //
            material[refMaterial].map_emissive = str;
		}
        else
		{
			//code non prit en charge
			_load_skipLine(flux);
		}
	}

	// --- composition de base

	for (size_t i = 0; i < material.size(); i++)
    {
        // >>> diffuse
        if (material[i].map_diffuse == "")
        {
            float r,g,b;

            name = material[i].name;
            // illum 2 ! simulation de la lumière phong / Ns 92
            r = sqrt( material[i].diffuse.r * 100.0f) * 0.1f;
            g = sqrt( material[i].diffuse.g * 100.0f) * 0.1f;
            b = sqrt( material[i].diffuse.b * 100.0f) * 0.1f;

            textureList.add(name, r,g,b, 1.0f);
            material[i].tex_diffuse = textureList.getID(name);
        }
        else
        {
            std::string path = ECO_fileGetPath(material[i].map_diffuse);
            std::string file = ECO_fileGetName(material[i].map_diffuse);
            std::string path_old = OTextureList::P_getPath();

            if (path != "") OTextureList::P_setPath(PATH_MESH+path); else OTextureList::P_setPath(PATH_MESH+m_path);
            textureList.add(file);
            material[i].tex_diffuse = textureList.getID(file);
            OTextureList::P_setPath(path_old);
        }

        // >>> texture bump de base qui remplacera les textures non chargées
        if (material[i].map_normal == "")
        {
            name = "normal_map";
            textureList.add(name, 0.5f, 0.5f, 1.0f, 1.0f); // ! attention à la valeur alpha = 1.0 !
            material[i].tex_normal = textureList.getID(name);
        }
        else
        {
            std::string path = ECO_fileGetPath(material[i].map_normal);
            std::string file = ECO_fileGetName(material[i].map_normal);
            std::string path_old = OTextureList::P_getPath();
            if (path != "") OTextureList::P_setPath(PATH_MESH+path); else OTextureList::P_setPath(PATH_MESH+m_path);
            textureList.add(file);
            material[i].tex_normal = textureList.getID(file);
            OTextureList::P_setPath(path_old);
        }

        // >>> texture specular de base qui remplacera les textures non chargées
        if (material[i].map_specular == "")
        {
            name = material[i].name + "_s";
            textureList.add(name, material[i].specular.r, material[i].specular.g, material[i].specular.b, 0.5f);
            material[i].tex_specular = textureList.getID(name);
        }
        else
        {
            std::string path = ECO_fileGetPath(material[i].map_specular);
            std::string file = ECO_fileGetName(material[i].map_specular);
            std::string path_old = OTextureList::P_getPath();
            if (path != "") OTextureList::P_setPath(PATH_MESH+path); else OTextureList::P_setPath(PATH_MESH+m_path);
            textureList.add(file);
            material[i].tex_specular = textureList.getID(file);
            OTextureList::P_setPath(path_old);
        }

        // >>> si texture emissive uniquement
        if (material[i].map_emissive != "")
        {
            std::string path = ECO_fileGetPath(material[i].map_emissive);
            std::string file = ECO_fileGetName(material[i].map_emissive);
            std::string path_old = OTextureList::P_getPath();
            if (path != "") OTextureList::P_setPath(PATH_MESH+path); else OTextureList::P_setPath(PATH_MESH+m_path);
            textureList.add(file);
            material[i].tex_emissive = textureList.getID(file);
            OTextureList::P_setPath(path_old);
        }
    }

	return true;
}

bool Tmeca::_load_mesh(std::ifstream& flux)
{
    std::string path, code, str;
	float x,y,z;
	int idx_obj = -1, mPart = 0;

	std::vector<OVec3_f> vertex;
	std::vector<OVec2_f> uv;
	std::vector<OVec3_f> normal;

    // les textures sont dans le répertoire (pas sous répertoire)
    OTextureList::P_setPath(path);
    OTextureList::P_setFilter(OTEXTURELIST_MESH);

    while( _load_readLine(flux) )
	{
        if (!(flux >> code)) return false; //erreur;
        //ECO_error_set("code %s", code.c_str());

		if (code == "#")
        {
            continue; //skip comments
        }
        // plus nombreux en premier
		else if (code == "v")
		{
		    flux >> x >> y >> z;
		    //convertion des y up en z up de notre systeme
		    //if (p_z_verticale)
		    //{
                vertex.push_back( OVec3_f(x, -z, y) );
		    //}
		    //else
            //{
            //    vertex.push_back( OVec3_f(x, y, z) );
            //}
		}
		else if (code == "vn") //process vertex normal
		{
		    flux >> x >> y >> z;
		    //if (p_z_verticale)
		    //{
                normal.push_back( OVec3_f(x, -z, y) );
		    //}
		    //else
            //{
            //    normal.push_back( OVec3_f(x, y, z) );
            //}
		}
		else if (code == "vt") //process uv texture
		{
            flux >> x >> y;
			uv.push_back( OVec2_f(x, y) );
		}
		else if (code == "f")
		{
            int v,t,n;
            // face à 3 (voire 99 !) vertices
            int i = 0;

            for (char c; i < 99; i++)
            {
                v = 0; t = 0; n = 0;
				//v uv n commencent à 1 !
				if (uv.size() == 1  && normal.size() == 1)
                    flux >> v;
                else if (uv.size() == 1)
                    flux >> v >> c >> c >> n;   // "//"
                else if (normal.size() == 1)
                    flux >> v >> c >> t;        // "/"
                else
                    flux >> v >> c >> t >> c >> n;
                if (!flux.good()) break;

                size_t i_som = partie[idx_obj].meshPart[mPart].vertex.size();

                partie[idx_obj].meshPart[mPart].vertex.push_back(vertex[v]);
                partie[idx_obj].meshPart[mPart].normal.push_back(normal[n]); // * material[partie[idx_obj].meshPart[mPart].material].tex_param.multBump);
                //
                OVec2_f uv2;
                uv2.x = uv[t].x; // * material[partie[idx_obj].meshPart[mPart].material].tex_param.repeatX;
                uv2.y = uv[t].y; // * material[partie[idx_obj].meshPart[mPart].material].tex_param.repeatY;
                partie[idx_obj].meshPart[mPart].uv.push_back(uv2);

                partie[idx_obj].meshPart[mPart].indice.push_back(i_som);
            }
            partie[idx_obj].meshPart[mPart].topologie.push_back(i);
		}
        // le mesh change de material = nouvelle partie de mesh
		else if (code == "usemtl")
		{
		    std::string name;
		    bool error = true;

            std::getline(flux, name);
            name = ECO_trim(name);

            //index material -> material en cours = les faces seront ajoutées à cette partie
            mPart = partie[idx_obj].meshPart.size();
            partie[idx_obj].meshPart.push_back(s_obj_part());

            error = true;
            // associer le material
            for (size_t i = 0; i < material.size(); i++)
            {
                if (material[i].name == name)
                {
                    partie[idx_obj].meshPart[mPart].material = i;
                    error = false;
                    //ECO_error_set("usemtl %s - %s", name.c_str(), material[i].map_diffuse.c_str());
                    break;
                }
            }
            if (error)
            {
                ECO_error_set("echec usemtl %s - %s >Tmeca.load_mesh", name.c_str(), m_file.c_str());
            }
		}
        else if (code == "obj")
        {
            idx_obj = -1;
            mPart = 0;
            vertex.clear();
            normal.clear();
            uv.clear();

            //index OBJ commence à 1 !
            vertex.push_back( OVec3_f(0,0,0) );
            normal.push_back( OVec3_f(0,0,0) );
            uv.push_back( OVec2_f(0,0) );

            // on recupere toute la ligne
            std::getline(flux, str);
            str = ECO_trim(str);

            //partie.push_back() dans load_anim
            for (size_t i_par = 0; i_par < partie.size(); i_par++)
            {
                if (partie[i_par].nom == str)
                {
                    idx_obj = i_par;
                    break;
                }
            }
            if (idx_obj == -1)
            {
                ECO_error_set("erreur %s sans partie déclarée > Tmeca.load_mesh", str.c_str());
                return false;
            }
        }
        else if (code == "end")
        {
            break;
        }
        flux.clear();
	}

	for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        //ECO_error_set("%s", partie[i_par].nom.c_str() );
        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            partie[i_par].meshPart[i_mes].topologie_style = partie[i_par].meshPart[i_mes].topologie[0];

            for (size_t i_top = 1; i_top < partie[i_par].meshPart[i_mes].topologie.size(); i_top++)
            {
                if (partie[i_par].meshPart[i_mes].topologie[i_top] != partie[i_par].meshPart[i_mes].topologie_style)
                {
                    partie[i_par].meshPart[i_mes].topologie_style = TOP_POLYGON;
                    break;
                }
            }
        }
    }
    return true;
}

bool Tmeca::_load_readLine(std::istream& flux)
{
	char next;
	while( flux >> std::skipws >> next )
    {
		flux.putback(next);
		if ('#' == next)
			_load_skipLine(flux);
		else
			return true;
    }

    return false;
}

void Tmeca::_load_skipLine(std::istream& flux)
{
    char next;
	flux >> std::noskipws;
    while( (flux >> next) && ('\n' != next) );
}





void Tmeca::incPos(float px, float py, float pz)
{
    m_pos.x += px;
    m_pos.y += py;
    m_pos.z += pz;
    _setModel();
}

void Tmeca::incRot(float rx, float ry, float rz)
{
    m_rot.x += rx;
    m_rot.y += ry;
    m_rot.z += rz;
    _setModel();
}





void Tmeca::save(std::ofstream& flux)
{
    flux << FLUX_MECA << " ";

    flux << m_code << " " << m_path << " " << m_file << " " << m_cull_face << " / ";
    flux << m_pos.x << " " << m_pos.y << " " << m_pos.z << " ";
    flux << m_rot.x << " " << m_rot.y << " " << m_rot.z << " / ";
    //
    flux << m_state << " " << m_action << " /" << std::endl;
}

// mec port_00 meca/ porte_00.txt 1 / -3 26 0 0 0 -90 / 2 port_00/open/ouvrir+chem_01_a1/state/1+coff_00/state/2 /
// = port_00/open/ouvrir+chem_01_a1/state/1+coff_00/state/2
// !!! remplace l'action existante
void Tmeca::setAction(std::string str)
{
    m_action = str;
}

void Tmeca::setAction_waiting(int value)
{
    m_action_waiting = value;
}

void Tmeca::setModel(OVec3_f& pos, OVec3_f& rot, OVec3_f scale)
{
    m_pos = pos;
    m_rot = rot;
    m_scale = scale;
    _setModel();
}


void Tmeca::_setModel()
{
    OMat4 model;

    // position globale
    model.loadIdentity();
    model.translate(m_pos);
    model.rotate(m_rot.z, 0,0,1);
    model.rotate(m_rot.y, 0,1,0);
    model.rotate(m_rot.x, 1,0,0);
    model.scale(m_scale);

    // repère
    model.translate(partie[0].axePos);
    model.rotate(partie[0].axeRot.z, 0,0,1);
    model.rotate(partie[0].axeRot.y, 0,1,0);
    model.rotate(partie[0].axeRot.x, 1,0,0);
    model.scale(partie[0].axeScale);
    partie[0].model = model;

    for (size_t i = 1; i < partie.size(); i++)
    {
        partie[i].model = partie[ partie[i].axeRef ].model;

        partie[i].model.translate(partie[i].axePos);
        partie[i].model.rotate(partie[i].axeRot.z, 0,0,1);
        partie[i].model.rotate(partie[i].axeRot.y, 0,1,0);
        partie[i].model.rotate(partie[i].axeRot.x, 1,0,0);
        partie[i].model.scale(partie[i].axeScale);
    }
}

void Tmeca::setPos(OVec3_f& pos)
{
	m_pos = pos;
	_setModel();
}

void Tmeca::setRot(OVec3_f& rot)
{
	m_rot = rot;
	_setModel();
}

void Tmeca::setScale(OVec3_f& scale)
{
	m_scale = scale;
	_setModel();
}

void Tmeca::setState_waiting(int value)
{
    m_state_waiting = value;
}

void Tmeca::setSelect()
{
    //if (!m_visible) return;
    m_select = !m_select;
}

void Tmeca::setSelect(bool mode)
{
    //if (!m_visible) return;
    m_select = mode;
}

void Tmeca::setState(int value)
{
    m_state = value;
}

void Tmeca::setVisible(bool mode)
{
    m_visible = mode;
}





void Tmeca::_vao()
{
    for (size_t i_par = 0; i_par < partie.size(); i_par++)
    {
        for (size_t i_mes = 0; i_mes < partie[i_par].meshPart.size(); i_mes++)
        {
            //génération de l'identifiant du VAO
            glGenVertexArrays(1, &partie[i_par].meshPart[i_mes].vao);
            //activation du VAO
            glBindVertexArray(partie[i_par].meshPart[i_mes].vao);

            // créations des buffers
            glGenBuffers(vao_nbr_buffer_obj, partie[i_par].meshPart[i_mes].vao_buffer);

            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[0]);
                glBufferData(GL_ARRAY_BUFFER, sizeof(OVec3_f) * partie[i_par].meshPart[i_mes].vertex.size(), &partie[i_par].meshPart[i_mes].vertex[0], GL_STATIC_DRAW);
                glEnableVertexAttribArray(LOCATION_POSITION);
                glVertexAttribPointer(LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0);

            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[1]);
                glBufferData(GL_ARRAY_BUFFER, sizeof(OVec3_f) * partie[i_par].meshPart[i_mes].normal.size(), &partie[i_par].meshPart[i_mes].normal[0], GL_STATIC_DRAW);
                glEnableVertexAttribArray(LOCATION_NORMAL);
                glVertexAttribPointer(LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 0, 0);

            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[2]);
                glBufferData(GL_ARRAY_BUFFER, sizeof(OVec2_f) * partie[i_par].meshPart[i_mes].uv.size(), &partie[i_par].meshPart[i_mes].uv[0], GL_STATIC_DRAW);
                glEnableVertexAttribArray(LOCATION_TEXCOORD);
                glVertexAttribPointer(LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, 0);

            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[LOCATION_MODEL]);
                for (int i = 0; i < 4 ; i++)
                {
                    glEnableVertexAttribArray(LOCATION_MODEL + i);
                    glVertexAttribPointer(LOCATION_MODEL + i, 4, GL_FLOAT, GL_FALSE, sizeof(OMat4), (const void*)(sizeof(float) * i * 4));
                    glVertexAttribDivisor(LOCATION_MODEL + i, 1);
                }

            // >>> il faut définir les buffers pour nos shaders avec bones
            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[7]);
                //glBufferData(GL_ARRAY_BUFFER, sizeof(uint16_t) * partie[i_par].meshPart[i_mes].boneWeight.size(), &partie[i_par].meshPart[i_mes].boneWeight[0], GL_STATIC_DRAW);
                glBufferData(GL_ARRAY_BUFFER, sizeof(uint16_t), 0, GL_STATIC_DRAW);
                // !!! notez glVertex(AttribI)Pointer
                glEnableVertexAttribArray(LOCATION_BONE);
                glVertexAttribIPointer(LOCATION_BONE, 4, GL_UNSIGNED_SHORT, 0, BUFFER_OFFSET(0));

            glBindBuffer(GL_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[8]);
                //glBufferData(GL_ARRAY_BUFFER, sizeof(float) * partie[i_par].meshPart[i_mes].weight.size(), &partie[i_par].meshPart[i_mes].weight[0], GL_STATIC_DRAW);
                glBufferData(GL_ARRAY_BUFFER, sizeof(float), 0, GL_STATIC_DRAW);
                glEnableVertexAttribArray(LOCATION_WEIGHT);
                glVertexAttribPointer(LOCATION_WEIGHT, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

            //---

            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, partie[i_par].meshPart[i_mes].vao_buffer[9]);
                glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * partie[i_par].meshPart[i_mes].indice.size(), &partie[i_par].meshPart[i_mes].indice[0], GL_STATIC_DRAW);

        }
    }

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


// ------------------------------
// --- gestion de l'animation ---
// ------------------------------


void Tmeca::anim()
{
    if (m_frame_end == 0) return;

    m_frame++;
    if (m_frame == m_frame_end)
    {
        m_frame = 0;
        m_loop--;
        if (m_loop < 1)
        {
            //m_frame_end = 0;
            detector.setState(DET_OFF);
            //m_etat_animation = MEC_ANIM_END;
            _setFin();
            return;
        }
    }

    for (size_t i = 0; i < partie.size(); i++)
    {
        partie[i].axePos = partie[i].decalPos;

        partie[i].axePos.x += partie[i].px[m_frame];
        partie[i].axePos.y += partie[i].py[m_frame];
        partie[i].axePos.z += partie[i].pz[m_frame];

        partie[i].axeRot = partie[i].decalRot;
        partie[i].axeRot.x += partie[i].rx[m_frame];
        partie[i].axeRot.y += partie[i].ry[m_frame];
        partie[i].axeRot.z += partie[i].rz[m_frame];

        partie[i].axeScale.x = partie[i].sx[m_frame];
        partie[i].axeScale.y = partie[i].sy[m_frame];
        partie[i].axeScale.z = partie[i].sz[m_frame];
    }

    _setModel();
}

int Tmeca::anim_start(std::string commande)
{
    std::vector<std::string> strList;
    int refAnimation = -1;

    // --- arret/pos neutre demandée
    if (commande == "neutre")
    {
        m_frame = 0;
        m_frame_end = 0;
        m_loop = 0;
        m_etat_animation = MEC_ANIM_NULL;
        m_state = MEC_FERME;
        _clear_model();
        return 0;
    }
    // --- animation en cours
    if (m_frame_end > 0) return MEC_ERROR_ANIM_RUN;

    // --- nouvelle animation
    m_frame = 0;
    m_frame_end = 0;
    m_etat_animation = MEC_ANIM_NULL;

    for (size_t i = 0; i < lstAnimation.size(); i++)
    {
        if (lstAnimation[i].find(commande) != std::string::npos)
        {
            refAnimation = i;
            break;
        }
    }
    if (refAnimation == -1)
    {
        ECO_error_set("animation non trouvée : %s >Tmeca", commande.c_str());
        return MEC_ERROR_REFANIMATION;
    }

    ECO_strCut(strList, lstAnimation[refAnimation], ' ');
    //ECO_error_set("animation : %s", lstAnimation[refAnimation].c_str());

    commande = strList[1];
    m_loop = ECO_strToInt(strList[2]);

    // setsound controle l'existence du fichier / remet state à null au besoin
    std::string str = strList[3];
    // ../module.wav = path relative = path mesh
    if (str.substr(0,3) == "../")
    {
        str = PATH_MESH + m_path + str.substr(3);
    }
    //ECO_error_set("meca sound %s > %d", str.c_str(), ECO_strToInt(strList[4]) );
    detector.setSound(str, ECO_strToInt(strList[4]), true); // le son est lancé
    detector.setState(DET_ON);

    return _setAction(commande);
}

/*
commande = nom ou cycle d'animation

nom loop (> 0) / animation (suivante 1 ou bloque 0)
alternance 99 / descendre 1 monter 1
tourne 1 / quart00 0 quart01 0 quart02 0 quart03 0
*/
int Tmeca::anim_start_cycle(std::string commande)
{
    std::vector<std::string> strList;

    // --- arret demandé
    m_frame = 0;
    m_frame_end = 0;
    m_etat_animation = MEC_ANIM_NULL;
    _clear_model();

    // --- decryptage
    ECO_strCut(strList, commande, ' ');

    if (strList.size() == 1)
    {
        int ref = -1;
        for (size_t i = 0; i < lstCycle.size(); i++)
        {
            if (lstCycle[i].find(commande) != std::string::npos)
            {
                ref = i;
                break;
            }
        }
        if (ref == -1)
        {
            ECO_error_set("cycle non trouvée : %s > Tmeca", commande.c_str());
            return MEC_ERROR_REFANIMATION;
        }
        strList.clear();
        ECO_strCut(strList, lstCycle[ref], ' ');
    }

    m_cycle.nom = strList[0];
    m_cycle.loop = ECO_strToInt(strList[1]);
    m_cycle.idx = 0;

    for (size_t i = 3; i < strList.size()-1; i++)
    {
        m_cycle.commande.push_back( strList[i] );
    }

    // lance l'animation
    return anim_start(m_cycle.commande[ m_cycle.idx ]);
}

void Tmeca::anim_stop()
{
    detector.setState(DET_OFF);
    anim_start("neutre");
}

int Tmeca::_setAction(std::string action)
{
    int refAction = -1;

    for (size_t i = 0; i < partie.size(); i++)
    {
        partie[i].px.clear();
        partie[i].py.clear();
        partie[i].pz.clear();
        partie[i].rx.clear();
        partie[i].ry.clear();
        partie[i].rz.clear();
        partie[i].sx.clear();
        partie[i].sy.clear();
        partie[i].sz.clear();

        partie[i].px.reserve(1000);
        partie[i].py.reserve(1000);
        partie[i].pz.reserve(1000);
        partie[i].rx.reserve(1000);
        partie[i].ry.reserve(1000);
        partie[i].rz.reserve(1000);
        partie[i].sx.reserve(1000);
        partie[i].sy.reserve(1000);
        partie[i].sz.reserve(1000);
    }

    for (size_t i = 0; i < lstAction.size(); i++)
    {
        if (lstAction[i].find(action) != std::string::npos)
        {
            refAction = i;
            // quelle partie ? + determine m_frame_end
            _setPartie(i);
        }
    }
    if (refAction == -1)
    {
        ECO_error_set("action non trouvée %s >Tmeca", action.c_str());
        return MEC_ERROR_REFACTION;
    }

    // ajuste tous les tableaux (sans modifier les valeurs!)
    for (size_t i = 0; i < partie.size(); i++)
    {
        partie[i].px.resize(m_frame_end, 0.0f);
        partie[i].py.resize(m_frame_end, 0.0f);
        partie[i].pz.resize(m_frame_end, 0.0f);
        partie[i].rx.resize(m_frame_end, 0.0f);
        partie[i].ry.resize(m_frame_end, 0.0f);
        partie[i].rz.resize(m_frame_end, 0.0f);
        partie[i].sx.resize(m_frame_end, 1.0f);
        partie[i].sy.resize(m_frame_end, 1.0f);
        partie[i].sz.resize(m_frame_end, 1.0f);
    }

    m_etat_animation = MEC_ANIM_RUN;

    return MEC_ERROR_NULL;
}

/*
ani ouvrir k0005 / o01 0 coffre_0.wav 0
ani quart null / %q 0 statue_0.wav 0
*/
void Tmeca::_setFin()
{
// si fin
    m_frame = 0;
    m_loop = 0;
    m_frame_end = 0;
    m_etat_animation = MEC_ANIM_END;
    detector.setState(DET_OFF);

    // changement d'etat sauf verrouille
    if (m_state == MEC_FERME)
    {
        m_state = MEC_OUVERT;
    }
    else if (m_state == MEC_OUVERT)
    {
        m_state = MEC_FERME;
    }

    if (m_cycle.loop > 0)
    {
        // animation suivante selon mode
        m_cycle.idx++;
        if (m_cycle.idx >= m_cycle.commande.size())
        {
            m_cycle.loop--;
            m_cycle.idx = 0;
        }
        if (m_cycle.loop > 0)
        {
            anim_start(m_cycle.commande[m_cycle.idx]);
        }
        // else anim_next_cycle
    }
}

//o01 herse pz 0 0.14 x 0.02 / 0.15 2.8 x 0.05
void Tmeca::_setPartie(int refAction)
{
    std::vector<std::string> strList;
    std::string axe = "px py pz rx ry rz sx sy sz";

    strList.clear();
    ECO_strCut(strList, lstAction[refAction], ' ');

    // tous les parties associés
    for (size_t i = 0; i < partie.size(); i++)
    {
        if (partie[i].nom == strList[1])
        {
            switch (axe.find(strList[2]))
            {
            case 0:
                _add_frame(strList, partie[i].px);
                break;
            case 3:
                _add_frame(strList, partie[i].py);
                break;
            case 6:
                _add_frame(strList, partie[i].pz);
                break;

            case 9:
                _add_frame(strList, partie[i].rx);
                break;
            case 12:
                _add_frame(strList, partie[i].ry);
                break;
            case 15:
                _add_frame(strList, partie[i].rz);
                break;

            case 18:
                _add_frame(strList, partie[i].sx);
                break;
            case 21:
                _add_frame(strList, partie[i].sy);
                break;
            case 24:
                _add_frame(strList, partie[i].sz);
                break;
            }
        }
    }
}

void Tmeca::_add_frame(std::vector<std::string>& strList, std::vector<float>& axe)
{
    float f0, f1, fval;
    float tps;

    for (size_t pas = 3; pas < strList.size(); pas += 5)
    {
        f0 = ECO_strToFloat(strList[pas]);
        f1 = ECO_strToFloat(strList[pas+1]);
        tps = ECO_strToFloat(strList[pas+3]) * p_fps;
        fval = (f1-f0) / tps;

        for (int f = 0; f < static_cast<int>(tps); f++)
        {
            axe.push_back(f0 + (fval * f));
        }
        if (m_frame_end < axe.size())
        {
            m_frame_end = axe.size();
        }
    }
}

// --- propriété ---

void Tmeca::P_setBoundScaleFactor(float value)
{
    p_bound_factor = value;
}









