#define STRICT

// Includes standard Windows
#include <windows.h>
#include <windowsx.h>
#include <time.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <stdio.h>

// Includes D3D
#define  D3D_OVERLOADS
#include <ddraw.h>
#include <d3d.h>
#include <d3dx.h>

// Includes utilitaires D3D
#include "d3dmath.h"
#include "d3dutil.h"
#include "D3DEnum.h"

// Ids Resources
//#include "resource.h"

// Constantes
#include "const.h"

// Types
#include "types.h"

// Variables globales projet
#include "vars.h"

// Prototypes fonctions autres modules
#include "proto.h"

// Macros
#include "macros.h"

void vSaveDirectX(void)
{
	int iTriangle, iMtrl;
    static TCHAR sFileName[512];
    FILE* hFile;

#ifndef _AMIGA_
    static TCHAR sInitialDir[512] = "";
    TCHAR sCurrentName[512] = "*.x";

	// Récupérer le directory courant
	GetCurrentDirectory(sizeof(sInitialDir), sInitialDir);

    // Préparer une structure OPENFILENAME
	OPENFILENAME sOFName = { sizeof(OPENFILENAME), NULL, NULL,
                         GetCatalogStr(hCat, MSG_0497, "Fichiers Scènes DirectX (*.x)\0*.x\0\0"),
                         NULL, 0, 1, sCurrentName, 512, sFileName, 512,
                         sInitialDir, GetCatalogStr(hCat, MSG_0498, "Ouvrir un fichier scène DirectX"), 0, 0, 1,
                         ".x", 0, NULL, NULL };

    // Choisir un fichier
    if(!GetOpenFileName(&sOFName))
        return;

    // Stocker le nom du directory pour le prochain appel
    strcpy(sInitialDir, sCurrentName);
#else
    strcpy(sFileName, "#?.x");
    if (!FSelect(GetCatalogStr(hCat, MSG_0501, "Ouvrir..."), sFileName))
        return;
#endif

    hFile = fopen(sFileName, "w");
    if (!hFile)
    {
        vTrace(GetCatalogStr(hCat, MSG_0503, "*** E0113 : ouverture fichier %s"), sFileName);
        return;
    }

    vTrace(GetCatalogStr(hCat, MSG_0504, "*** Fichier scène ouvert : %s"), sFileName);

    // Garbage collecter
    vCollect();

	// Créer le header
	fprintf(hFile, "xof 0303txt 0032\n\nHeader {\n1;\n0;\n1;\n}\n\n");

	// Faire la liste des materials
	for (iMtrl = 0 ; iMtrl < iMtrlFirstAvailable ; iMtrl ++)
	{
		D3DMATERIAL7 *hmtrl = &(Materials[iMtrl].mtrl);
		fprintf(hFile, "Material Material_%d {\n%f, %f, %f, %f;;\n%f;\n%f, %f, %f;;\n%f, %f, %f;;\n}\n\n",
			iMtrl,
			hmtrl -> diffuse.r, hmtrl -> diffuse.g, hmtrl -> diffuse.b, hmtrl -> diffuse.a,
			hmtrl -> power,
			hmtrl -> specular.r, hmtrl -> specular.g, hmtrl -> specular.b,
  			hmtrl -> emissive.r, hmtrl -> emissive.g, hmtrl -> emissive.b
			);
	}

	// Entête frame
	fprintf(hFile, "Frame __sKulpt {\n");
	fprintf(hFile, "FrameTransformMatrix {\n");	
	fprintf(hFile, "1.000000,0.000000,0.000000,0.000000;,\n");
	fprintf(hFile, "0.000000,1.000000,0.000000,0.000000;,\n");
	fprintf(hFile, "0.000000,0.000000,1.000000,0.000000;,\n");
	fprintf(hFile, "0.000000,2.396835,0.000000,1.000000;;\n");
	fprintf(hFile, "}\n\n");

	// Entête mesh
	fprintf(hFile, "Mesh __sKulptShape {\n");

	// Vertices
	fprintf(hFile, "%d;\n", iVertFirstAvailable);
	for (int iVert = 0 ; iVert < iVertFirstAvailable ; iVert++)
	{
		fprintf(hFile, "%f;%f;%f;",
			Vertices[iVert].vPoint.x, Vertices[iVert].vPoint.y, Vertices[iVert].vPoint.z);
		if (iVert == iVertFirstAvailable - 1)
			fprintf(hFile, ";\n\n");
		else
			fprintf(hFile, ",\n");
	}

	// Triangles
	fprintf(hFile, "%d;\n", iTriaFirstAvailable);
	for (iTriangle = 0 ; iTriangle < iTriaFirstAvailable ; iTriangle++)
	{
		fprintf(hFile, "3;%d,%d,%d;",
			Triangles[iTriangle].iSommets[0], Triangles[iTriangle].iSommets[1], Triangles[iTriangle].iSommets[2]);
		if (iTriangle == iTriaFirstAvailable - 1)
			fprintf(hFile, ";\n\n");
		else
			fprintf(hFile, ",\n");
	}

	// Mesh Materials
	fprintf(hFile, "MeshMaterialList {\n%d;\n%d;\n", iMtrlFirstAvailable, iTriaFirstAvailable);
	for (iTriangle = 0 ; iTriangle < iTriaFirstAvailable ; iTriangle++)
	{
		fprintf(hFile, "%d", Triangles[iTriangle].iMtrl);
		if (iTriangle == iTriaFirstAvailable - 1)
			fprintf(hFile, ";;\n");
		else
			fprintf(hFile, ",\n");
	}
	for (iMtrl = 0 ; iMtrl < iMtrlFirstAvailable ; iMtrl++)
		fprintf(hFile, "{Material_%d}\n", iMtrl);
	fprintf(hFile, "}\n\n");

	// Fin de mesh
	fprintf(hFile, "}\n\n");

	// fin de frame
	fprintf(hFile, "}\n\n");

	// Animationset
	fprintf(hFile, "AnimationSet skulpt_animset {\n");
	fprintf(hFile, "}\n");


	fclose(hFile);
}

static inline BOOL bStartBy(char *s1, char *s2)
{
    if (!s1 || !s2 || !strlen(s1) || !strlen(s2)) return FALSE;
    return !strncmp(s1, s2, strlen(s2));
}

void vLoadDirectX(void)
{
    static TCHAR sFileName[512], cBuffer[512];
    FILE* hFile;
#ifndef _AMIGA_
    static TCHAR sInitialDir[512] = "";
    TCHAR sCurrentName[512] = "*.x";

	// Récupérer le directory courant
	GetCurrentDirectory(sizeof(sInitialDir), sInitialDir);

    // Préparer une structure OPENFILENAME
	OPENFILENAME sOFName = { sizeof(OPENFILENAME), NULL, NULL,
                         GetCatalogStr(hCat, MSG_0535, "Fichiers Scènes DirectX (*.x)\0*.x\0\0"),
                         NULL, 0, 1, sCurrentName, 512, sFileName, 512,
                         sInitialDir, GetCatalogStr(hCat, MSG_0536, "Ouvrir un fichier scène DirectX"), 0, 0, 1,
                         ".x", 0, NULL, NULL };

    // Choisir un fichier
    if(!GetOpenFileName(&sOFName))
        return;

    // Stocker le nom du directory pour le prochain appel
    strcpy(sInitialDir, sCurrentName);
#else
    strcpy(sFileName, "#?.x");
    if (!FSelect(GetCatalogStr(hCat, MSG_0539, "Ouvrir..."), sFileName))
        return;
#endif

    hFile = fopen(sFileName, "r");
    if (!hFile)
    {
        vTrace(GetCatalogStr(hCat, MSG_0541, "*** E0114 : ouverture fichier %s"), sFileName);
        return;
    }

    vTrace(GetCatalogStr(hCat, MSG_0542, "*** Fichier scène ouvert : %s"), sFileName);

    // Effacer tous les objets présents en mémoire
    vDeleteObjects();

#define STATE_IDLE                  0
#define STATE_GET_MESH              1
#define STATE_GET_VERTICES          2
#define STATE_GET_FACES             3
#define STATE_GET_MTRLL_NUM_MAT     4
#define STATE_GET_MTRLL_NUM_FACES   5
#define STATE_GET_MTRLL_FACES       6
#define STATE_GET_MTRLL_MTRL        7
#define STATE_GET_MTRL_DIFFUSE      8
#define STATE_GET_MTRL_POWER        9
#define STATE_GET_MTRL_SPECULAR     10
#define STATE_GET_MTRL_EMISSIVE     11
#define STATE_GET_MTRL_END          12
#define STATE_GET_NCOORDS           13
#define STATE_GET_COORDS            14
#define STATE_GET_TRANSMAT          15
#define STATE_END_TRANSMAT          16

	// lire le fichier
    BOOL bSkip = FALSE, bError = FALSE, bMeshVert = FALSE;
    int iState = STATE_IDLE,
        iSkipLevel = 0,
        iLine = 0,
        nVert = 0, iVert = 0,
        nTri = 0,  iTri = 0,
        nMtrl, iMtrl = 0,
        nMtrlTri, iMtrlTri = 0,
        nCoords, iCoords = 0,
        *iFirstTriangleOfFan = NULL,
        i1, i2, i3, iVal, iDelta = 0,
        iBaseVert = 0,
        iBaseFace = 0,
        iBaseMtrl = 5,      // Il existe déjà 5 matériaux de base (0-4)
        iFrame = 0;
    D3DMATERIAL7    sMtrl;
    float f1, f2, f3, f4;
	float fEnvXmin = 2000000000., fEnvXmax = -2000000000., fEnvSize;
	float fEnvYmin = 2000000000., fEnvYmax = -2000000000.;
	float fEnvZmin = 2000000000., fEnvZmax = -2000000000.;

    D3DMATRIX   MatrixStack[10];

    // Parcourir le fichier
    do
    {
        char *p;

        // Lire une ligne et incrémenter le compteur
        if (!fgets(cBuffer, sizeof(cBuffer), hFile)) goto __Done;
        iLine++;

        // Supprimer les commentaires
        if (p = strstr(cBuffer ,"//")) *p = 0;

        // Supprimer les retours chariot, les tabulations, les espaces et les ",; EN FIN DE LIGNE
        while (strlen(cBuffer) && strchr("\r\n\t,;\" ", cBuffer[strlen(cBuffer) - 1])) cBuffer[strlen(cBuffer) - 1] = 0;

        // transformer les ',' en ';'
        while (p = strchr(cBuffer, ',')) *p = ';';

        // Supprimer les espaces et " en début de ligne
        while (strlen(cBuffer) && (isspace(cBuffer[0]) || cBuffer[0] == '"')) strcpy(cBuffer, cBuffer + 1);

        // S'il reste quelque chose dans la ligne
        if (strlen(cBuffer))
        {
            // Si on est en mode skip, sauter la ligne jusqu'à avoir dépilé la dernière '}'
            if (bSkip)
            {
                if (strchr(cBuffer, '{')) iSkipLevel++;

                if (strchr(cBuffer, '}'))
                {
                    if (!iSkipLevel)
                        bSkip = FALSE;
                    else
                        iSkipLevel--;
                }

                goto __Done;
            }

            // Passer la chaîne en minuscules
            p = cBuffer;
            while (*p = tolower(*p)) p++;

            // Si on est sur la première ligne, vérifier l'en-tête
            if (iLine == 1)
            {
                if (!(bStartBy(cBuffer, "xof 0302txt 0064") || bStartBy(cBuffer, "xof 0303txt 0032")))
                {
                    vTrace(GetCatalogStr(hCat, MSG_0547, "*** E0115 : en tête fichier incorrecte"));
                    bError = TRUE;
                }
                goto __Done;
            }

            // Parties non traitées : passer en mode skip pour les sauter
            if (
                bStartBy(cBuffer, "template") ||
                bStartBy(cBuffer, "meshnormals") ||
                bStartBy(cBuffer, "header") ||
                bStartBy(cBuffer, "animationset ")
               )
            {
                // aller jusqu'au '{', qui peut être sur une ligne plus loin
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);
                bSkip = TRUE;
                goto __Done;
            }

            // Frame : Incrémenter le compteur de nesting frames après avoir réglé la transformmatrix sur l'identité
            if (bStartBy(cBuffer, "frame "))
            {
                vTrace(GetCatalogStr(hCat, MSG_0553, "Décodage frame [%s], frame nesting %d"), strchr(cBuffer, ' ') ? strchr(cBuffer, ' ') + 1 : "<noname>", iFrame);
                D3DUtil_SetIdentityMatrix(MatrixStack[iFrame++]);
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);
                goto __Done;
            }

            // frametransformmatrix : lire la matrice
            if (bStartBy(cBuffer, "frametransformmatrix"))
            {
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);
                iState = STATE_GET_TRANSMAT;
                i1 = 0;
                goto __Done;
            }

            // transformmatrix, lecture d'une ligne de la matrice. Si le compte est bon, revenir dans IDLE
            if (iState == STATE_GET_TRANSMAT && (4 == sscanf(cBuffer, "%f;%f;%f;%f", &f1, &f2, &f3, &f4)))
            {
                MatrixStack[iFrame - 1].m[i1][0] = f1;
                MatrixStack[iFrame - 1].m[i1][1] = f2;
                MatrixStack[iFrame - 1].m[i1][2] = f3;
                MatrixStack[iFrame - 1].m[i1][3] = f4;
                if (++i1 >= 4)
                {
                    // Rétablir la matrice Right-Hand
                    MatrixStack[iFrame - 1].m[0][2] *= -1.0f;
                    MatrixStack[iFrame - 1].m[2][0] *= -1.0f;
                    MatrixStack[iFrame - 1].m[1][2] *= -1.0f;
                    MatrixStack[iFrame - 1].m[2][1] *= -1.0f;
                    MatrixStack[iFrame - 1].m[3][2] *= -1.0f;

                    iState = STATE_END_TRANSMAT;
                }
                goto __Done;
            }

            // Mesh : réinitialiser les compteurs de vert, tri, passer en attente du nombre de vertices
            if (iState == STATE_IDLE && bStartBy(cBuffer, "mesh "))
            {
                iState = STATE_GET_MESH;
                iVert = iTri = iDelta = 0;
                vTrace(GetCatalogStr(hCat, MSG_0558, "\tDécodage mesh [%s]"), strchr(cBuffer, ' ') ? strchr(cBuffer, ' ') + 1 : "<noname>");
                bMeshVert = FALSE;
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);
                goto __Done;
            }

            if (iState == STATE_GET_MESH)
            {
                // Si on n'a pas lu le nombre de vertices, le lire et passer en attente des vertices
                if (!bMeshVert && (iVal = atoi(cBuffer)))
                {
                    nVert = iVal;
                    iState = STATE_GET_VERTICES;
                    bMeshVert = TRUE;
                    goto __Done;
                }

                // Si on n'a pas lu le nombre de triangles, le lire et passer en attente des triangles
                if (bMeshVert && (iVal = atoi(cBuffer)))
                {
                    nTri = iVal;

                    // Allocation table mapping fan -> tri
                    bError = !(iFirstTriangleOfFan = (int *) malloc(nTri * sizeof(int)));

                    iState = STATE_GET_FACES;
                    goto __Done;
                }

                // MeshMaterialList : passer en attente du nombre de matériaux
                if (bStartBy(cBuffer, "meshmateriallist"))
                {
                    iState = STATE_GET_MTRLL_NUM_MAT;
                    while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);

                    goto __Done;
                }
            }

            // Mesh, lecture d'un vertex. Si le compte est bon, revenir dans MESH
            if (iState == STATE_GET_VERTICES && (3 == sscanf(cBuffer, "%f;%f;%f", &f1, &f2, &f3)))
            {
                D3DVECTOR vVect = D3DVECTOR(f1, f2, f3);

                // Appliquer la pile de matrices de transformation
                for (int iCnt = 0 ; iCnt < iFrame ; iCnt++)
                    D3DMath_VectorMatrixMultiply(vVect, vVect, MatrixStack[iCnt]);

                iMakeVertex(vVect, XDC_FORCENEW);

                f1 = vVect.x ; f2 = vVect.y ; f3 = vVect.z;
				if (fEnvXmin > f1) fEnvXmin = f1; if (fEnvXmax < f1) fEnvXmax = f1;
				if (fEnvYmin > f2) fEnvYmin = f2; if (fEnvYmax < f2) fEnvYmax = f2;
				if (fEnvZmin > f3) fEnvZmin = f3; if (fEnvZmax < f3) fEnvZmax = f3;

                if (++iVert >= nVert) iState = STATE_GET_MESH;
                goto __Done;
            }

            // Mesh, lecture d'un triangle fan
            if (iState == STATE_GET_FACES)
            {
                int iNum;
                if (3 == sscanf(cBuffer, "%d;%d;%d;", &iNum, &i1, &i2) && (p = strchr(cBuffer, ';') + 1) && (p = strchr(p, ';') + 1) && (p = strchr(p, ';') + 1))
                {  
                    i1 += iBaseVert;
                    i2 += iBaseVert;

                    // Mémoriser
                    iDelta += iNum - 3;
                    iNum = 0;

                    // Pour chaque point du fan, créer le triangle (point, v[i1], v[i2])
                    while (p && 1 == sscanf(p, "%d", &i3) && p)
                    {
                        i3 += iBaseVert;
                        // Si on a plus d'un triangle dans le fan, mettre les suivants à material -1,
                        // pour ensuite les lier au material qui sera défini pour le premier triangle
                        // du fan (malheureusement plus loin !-()
                        iVal = iMakeTriangle(i1, i2, i3, iNum ? -1 : 0);

                        // Décaler le fan
                        i2 = i3;
                        p = strchr(p, ';') ? strchr(p, ';') + 1 : 0;

                        // Se rappeler qu'on a fait le premier triangle du fan (les prochains dans ce while
                        // seront affectés material -1, cf. ci-dessus)
                        if (!iNum)
                        {
                            iFirstTriangleOfFan[iTri] = iVal;
                            iNum++;
                        }
                    }
                }

                // Si on a le compte de triangles, revenir en attente des structures MESH
                if (++iTri >= nTri) iState = STATE_GET_MESH;
                goto __Done;
            }

            // MeshMaterialList : Lecture nombre de matériaux et passage en attente du nombre de faces
            if (iState == STATE_GET_MTRLL_NUM_MAT)
            {
                if (!(1 == sscanf(cBuffer, "%d", &nMtrl) && nMtrl))
                {
                    bError = TRUE;
                    vTrace(GetCatalogStr(hCat, MSG_0565, "*** E0094 : lecture nombre matériaux"));
                    goto __Done;
                }
                iState = STATE_GET_MTRLL_NUM_FACES;
                goto __Done;
            }

            // MeshMaterialList : Lecture nombre de faces et passage en attente des faces
            if (iState == STATE_GET_MTRLL_NUM_FACES)
            {
                if (!(1 == sscanf(cBuffer, "%d", &nMtrlTri) && nMtrlTri))
                {
                    bError = TRUE;
                    vTrace(GetCatalogStr(hCat, MSG_0567, "*** E0095 : lecture nombre faces matériaux"));
                    goto __Done;
                }
                iMtrlTri = 0;
                iState = STATE_GET_MTRLL_FACES;
                goto __Done;
            }

            // MeshMaterialList : lecture des faces
            if (iState == STATE_GET_MTRLL_FACES && (1 == sscanf(cBuffer, "%d", &i1)))
            {
                // Application material sur la première face du FAN (les autres seront fillées à la fin du mesh)
                Triangles[iFirstTriangleOfFan[iMtrlTri]].iMtrl = i1 + iBaseMtrl;

                // Dirty hack pour charger car.x
                if (nMtrlTri == 1 && nTri > 1)
                {
                    vTrace("\tSpread material");
                    for (int iTriangle = 0 ; iTriangle < nTri + iDelta ; iTriangle++)
                        Triangles[iTriangle + iBaseFace].iMtrl = i1 + iBaseMtrl;
                }

                // Si on a le compte, attendre les materials
                if (++iMtrlTri >= nMtrlTri) iState = STATE_GET_MTRLL_MTRL;
                goto __Done;
            }

            // Material dans MeshMaterialList : passer en attente de la définition DIFFUSE
            if (iState == STATE_GET_MTRLL_MTRL && bStartBy(cBuffer, "material"))
            {
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);
                iState = STATE_GET_MTRL_DIFFUSE;
                ZeroMemory(&sMtrl, sizeof(D3DMATERIAL7));
                goto __Done;
            }

            // Material : lecture diffuse et attente power
            if (iState == STATE_GET_MTRL_DIFFUSE
                 && (4 == sscanf(cBuffer, "%f;%f;%f;%f",
                                 &sMtrl.diffuse.r, &sMtrl.diffuse.g, &sMtrl.diffuse.b, &sMtrl.diffuse.a
                                )
                    )
               )
            {
                iState = STATE_GET_MTRL_POWER;
                goto __Done;
            }

            // Material : lecture power et attente specular
            if (iState == STATE_GET_MTRL_POWER
                 && (1 == sscanf(cBuffer, "%f",
                                 &sMtrl.power
                                )
                    )
               )
            {
                iState = STATE_GET_MTRL_SPECULAR;
                goto __Done;
            }

            // Material : lecture specular et attente emissive
            if (iState == STATE_GET_MTRL_SPECULAR
                 && (3 == sscanf(cBuffer, "%f;%f;%f",
                                 &sMtrl.specular.r, &sMtrl.specular.g, &sMtrl.specular.b
                                )
                    )
               )
            {
                sMtrl.specular.a = 1;
                iState = STATE_GET_MTRL_EMISSIVE;
                goto __Done;
            }

            // Material : lecture emissive, création material, attente fin material
            if (iState == STATE_GET_MTRL_EMISSIVE
                 && (3 == sscanf(cBuffer, "%f;%f;%f",
                                 &sMtrl.emissive.r, &sMtrl.emissive.g, &sMtrl.emissive.b
                                )
                    )
               )
            {
                sMtrl.emissive.a = 1;
                iState = STATE_GET_MTRL_END;
                sprintf(cBuffer, "X-Mat %d", i1 + iBaseMtrl);
                iMtrl = iMakeMaterial(&sMtrl, cBuffer, XDC_FORCENEW);
                goto __Done;
            }

            if (iState == STATE_GET_MTRL_END && bStartBy(cBuffer, "texturefilename"))
            {
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);

                if (!strchr(cBuffer, '}')) fgets(cBuffer, sizeof(cBuffer), hFile);

                // Supprimer les commentaires
                if (p = strstr(cBuffer ,"//")) *p = 0;

                // Supprimer les retours chariot, les tabulations, les espaces et les ",; EN FIN DE LIGNE
                while (strlen(cBuffer) && strchr("\r\n\t,;\" ", cBuffer[strlen(cBuffer) - 1])) cBuffer[strlen(cBuffer) - 1] = 0;

                // Supprimer les espaces et " en début de ligne
                while (strlen(cBuffer) && (isspace(cBuffer[0]) || cBuffer[0] == '"')) strcpy(cBuffer, cBuffer + 1);

                strncpy(Materials[iMtrl].sTexName, cBuffer, sizeof(Materials[iMtrl].sTexName));
                Materials[iMtrl].bTextured = TRUE;

                while (!strchr(cBuffer, '}')) fgets(cBuffer, sizeof(cBuffer), hFile);

                goto __Done;
            }

            if (iState == STATE_GET_MESH && bStartBy(cBuffer, "meshtexturecoords"))
            {
                while (!strchr(cBuffer, '{')) fgets(cBuffer, sizeof(cBuffer), hFile);

                iState = STATE_GET_NCOORDS;
                goto __Done;
            }

            if (iState == STATE_GET_NCOORDS
                 && (1 == sscanf(cBuffer, "%d",
                                 &nCoords
                                )
                    )
               )
            {
                iState = STATE_GET_COORDS;
                iCoords = 0;
                goto __Done;
            }

            if (iState == STATE_GET_COORDS
                 && (2 == sscanf(cBuffer, "%f;%f",
                                 &f1, &f2
                                )
                    )
               )
            {
                Vertices[iBaseVert + iCoords].vPointBack.x = f1;
                Vertices[iBaseVert + iCoords].vPointBack.y = f2;
                // Si on a le compte de coordonnées, revenir en attente de fin de coordonnées
                if (++iCoords >= nCoords) iState = STATE_GET_NCOORDS;
                goto __Done;
            }

            if (*cBuffer == '}')
            {
                switch(iState)
                {
                    case STATE_GET_MTRL_END :   // Si on a fini le matériau, revenir aux matériaux du MeshMaterialList
                        iState = STATE_GET_MTRLL_MTRL;
                        goto __Done;

                    case STATE_END_TRANSMAT :
                        iState = STATE_IDLE;
                        goto __Done;

                    case STATE_GET_NCOORDS :     // Si on a fini le TextureCoords, revenir au mesh
                    case STATE_GET_MTRLL_MTRL : // Si on a fini le MeshMaterialList, revenir au mesh
                        iState = STATE_GET_MESH;
                        goto __Done;

                    case STATE_GET_MESH :       // Si on a fini le mesh, revenir en idle après avoir consolidé les compteurs globaux
                        vTrace(GetCatalogStr(hCat, MSG_0582, "\tFin mesh : ajout %d vertices, %d trifans, %d triangles, %d matériaux"), nVert, nTri, nTri + iDelta, nMtrl);

                        // Filler les triangles non fillés parce qu'ils n'étaient pas les premiers des fans
                        for (i1 = 0 ; i1 < nTri + iDelta ; i1++)
                        {
                            int iMat = 0;
                            if (Triangles[i1 + iBaseFace].iMtrl >= 0)
                                iMat = Triangles[i1 + iBaseFace].iMtrl;
                            else
                                Triangles[i1 + iBaseFace].iMtrl = iMat;
                        }

                        iBaseVert += nVert;
                        iBaseFace += (nTri + iDelta);
                        iBaseMtrl += nMtrl;
                        if (iFirstTriangleOfFan)
                        {
                            free(iFirstTriangleOfFan);
                            iFirstTriangleOfFan = NULL;
                        }
                        iState = STATE_IDLE;
                        goto __Done;

                    case STATE_IDLE :
                        if (iFrame > 0)
                        {
                            iFrame--;
                            vTrace("Frame nesting %d", iFrame);
                            goto __Done;
                        }
                }

                // Autres cas
                vTrace(GetCatalogStr(hCat, MSG_0584, "*** E0116 : erreur automate d'état, '}' sur état %d et frame %d"), iState, iFrame);
                bError = TRUE;
                goto __Done;
            }

            vTrace(GetCatalogStr(hCat, MSG_0585, "**** I0000 : ligne %d non traitée : [%s] (state = %d, error = %d)"), iLine, cBuffer, iState, bError);
        } // endif (strlen(cBuffer))
__Done:
        ;
    }
    while (!feof(hFile) && !bError);

	fclose(hFile);

    if (iFirstTriangleOfFan) free(iFirstTriangleOfFan);

    if (bError)
    {
        vDeleteObjects();
        return;
    }

	// Calculer la plus grande dimension de la scène
	fEnvSize = fEnvXmax - fEnvXmin;
	if (fEnvYmax - fEnvYmin > fEnvSize) fEnvSize = fEnvYmax - fEnvYmin;
	if (fEnvZmax - fEnvZmin > fEnvSize)	fEnvSize = fEnvZmax - fEnvZmin;

    // Normaliser les coordonnées de tous les points lus
	for (int iVertex = 0 ; iVertex < iBaseVert + nVert ; iVertex++)
	{
		Vertices[iVertex].vPoint.x = ((Vertices[iVertex].vPoint.x - fEnvXmin - (fEnvXmax - fEnvXmin) / 2.f) * 8.f / fEnvSize);
		Vertices[iVertex].vPoint.y = ((Vertices[iVertex].vPoint.y - fEnvYmin - (fEnvYmax - fEnvYmin) / 2.f) * 8.f / fEnvSize);
		Vertices[iVertex].vPoint.z = ((Vertices[iVertex].vPoint.z - fEnvZmin - (fEnvZmax - fEnvZmin) / 2.f) * 8.f / fEnvSize);
    }

    // Référencer les matériaux aux textures
    vXRefMaterials2Textures();

    // Mapper les u/v des vertices sur les triangles
    for (int iTriangle = 0 ; iTriangle <= iTriaLastUsed ; iTriangle++)
    {
        gTri    *hTri   = &(Triangles[iTriangle]);
        gMtrl   *hMtrl  = &(Materials[hTri -> iMtrl]);
        gTex    *hTex   = &(Textures[hMtrl -> iTexture]);

        for (i1 = 0 ; i1 < 3 ; i1++)
        {
            gSommet *hPoint = &(Vertices[hTri -> iSommets[i1]]);
            hTri -> u[i1] = (short) (hPoint -> vPointBack.x * (float) (hTex -> iWidth - 1));
            hTri -> v[i1] = (short) (hPoint -> vPointBack.y * (float) (hTex -> iHeight - 1));
        }
    }

    // Redessiner la 3D
    vForce3DRefresh(XDC_MODE_COMPLET);
}

