//****************************************************************************
// Local                fffWriteTriObjectChunk_local
//****************************************************************************
static bool fffWriteTriObjectChunk_local(

    FILE                        *fptr,
    long                        &chunkSize,
    const map<string,string>    &textureInfo,
    unsigned long               objId,
    const FffMultiObjectC       &model)
{
    long chunkStartPos = ftell(fptr);
    unsigned short id = TRI_OBJECT;
    chunkSize = 6;
    if (!fffWriteChunkHeader_local(fptr,id,chunkSize))
        return false;

    unsigned short numVtx = (unsigned short) model.numPoints(objId);
    if (numVtx != 0)
    {
        id = TRI_POINT_ARRAY;
        long ptChunkSize = 6+sizeof(unsigned short)+numVtx*sizeof(float)*3;
        if (!fffWriteChunkHeader_local(fptr,id,ptChunkSize))
            return false;

        if (fwrite(&numVtx,sizeof(unsigned short),1,fptr) != 1)
            return false;

        const vector<FgVect3F> &ptList = model.getPtList(objId);
        for (unsigned short ii=0; ii<numVtx; ++ii)
        {
            if (fwrite(&ptList[ii][0],sizeof(float),1,fptr) != 1)
                return false;
            if (fwrite(&ptList[ii][1],sizeof(float),1,fptr) != 1)
                return false;
            if (fwrite(&ptList[ii][2],sizeof(float),1,fptr) != 1)
                return false;
        }

        chunkSize += ptChunkSize;
    }

    // Check if it is per vertex texture
    bool perVertexTexture =
        (model.numPoints(objId) == model.numTxtCoord(objId) &&
         model.numTxtTris(objId) == 0 &&
         model.numTxtQuads(objId) == 0);
    if (!perVertexTexture)
    {
        if (model.numTriangles(objId) == model.numTxtTris(objId) &&
                model.numQuads(objId) == model.numTxtQuads(objId))
        {
            if (model.numPoints(objId) == model.numTxtCoord(objId))
            {
                bool identical = true;

                // Check if the facet lists are identical
                const vector<FgVect3UI> &triList =
                    model.getTriList(objId);
                const vector<FgVect3UI> &txtTriList =
                    model.getTexTriList(objId);
                for (unsigned long tri=0;
                        identical && tri<model.numTxtTris(objId); ++tri)
                {
                    if (triList[tri] != txtTriList[tri])
                        identical = false;
                }

                const vector<FgVect4UI> &quadList =
                    model.getQuadList(objId);
                const vector<FgVect4UI> &txtQuadList =
                    model.getTexQuadList(objId);
                for (unsigned long quad=0;
                        identical && quad<model.numTxtQuads(objId); ++quad)
                {
                    if (quadList[quad] != txtQuadList[quad])
                        identical = false;
                }

                perVertexTexture = identical;

                if (!identical)
                    cout << "Skipping per-facet texture data\n" << flush;
            }
            else if (model.numTxtCoord(objId) != 0)
                cout << "Skipping per-facet texture data\n" << flush;
        }
    }

    if (perVertexTexture && model.numTxtCoord(objId) != 0)
    {
        id = TRI_TEX_COORD;
        long ptChunkSize = 6+sizeof(unsigned short)+numVtx*sizeof(float)*2;
        if (!fffWriteChunkHeader_local(fptr,id,ptChunkSize))
            return false;

        if (fwrite(&numVtx,sizeof(unsigned short),1,fptr) != 1)
            return false;

        const vector<FgVect2F> &ptList = model.getTextCoord(objId);
        for (unsigned short ii=0; ii<numVtx; ++ii)
        {
            if (fwrite(&ptList[ii][0],sizeof(float),1,fptr) != 1)
                return false;
            if (fwrite(&ptList[ii][1],sizeof(float),1,fptr) != 1)
                return false;
        }

        chunkSize += ptChunkSize;
    }

    // Now save facet info.
    unsigned short numTris = (unsigned short) model.numTriangles(objId);
    unsigned short numQuads = (unsigned short) model.numQuads(objId);
    unsigned short totalTris = numTris + numQuads*2;
    if (totalTris != 0)
    {
        long triChunkStartPos = ftell(fptr);

        // Facet (tris) info.
        unsigned short fourthNum = 7;   // The first 3 bits turned on.
        id = TRI_FACE_ARRAY;
        long smoothGrpChunkSize = totalTris*sizeof(unsigned long)+6;
        long triChunkSize = 6 + sizeof(unsigned short)
                            + totalTris*sizeof(unsigned short)*4
                            + smoothGrpChunkSize;
        if (!fffWriteChunkHeader_local(fptr,id,triChunkSize))
            return false;

        if (fwrite(&totalTris,sizeof(unsigned short),1,fptr) != 1)
            return false;

        const vector<FgVect3UI> &triList = model.getTriList(objId);
        for (unsigned short ii=0; ii<numTris; ++ii)
        {
            unsigned short idx1 = (unsigned short) triList[ii][0];
            unsigned short idx2 = (unsigned short) triList[ii][1];
            unsigned short idx3 = (unsigned short) triList[ii][2];

            if (fwrite(&idx1,sizeof(unsigned short),1,fptr) != 1)
                return false;
            if (fwrite(&idx2,sizeof(unsigned short),1,fptr) != 1)
                return false;
            if (fwrite(&idx3,sizeof(unsigned short),1,fptr) != 1)
                return false;
            if (fwrite(&fourthNum,sizeof(unsigned short),1,fptr) != 1)
                return false;
        }
        const vector<FgVect4UI> &quadList = model.getQuadList(objId);
        ushort ii;
        for (ii=0; ii<numQuads; ++ii)
        {
            for (int xx=0; xx<2; ++xx)
            {
                unsigned short idx1 = (unsigned short) quadList[ii][0];
                unsigned short idx2 = (unsigned short) quadList[ii][xx+1];
                unsigned short idx3 = (unsigned short) quadList[ii][xx+2];

                if (fwrite(&idx1,sizeof(unsigned short),1,fptr) != 1)
                    return false;
                if (fwrite(&idx2,sizeof(unsigned short),1,fptr) != 1)
                    return false;
                if (fwrite(&idx3,sizeof(unsigned short),1,fptr) != 1)
                    return false;
                if (fwrite(&fourthNum,sizeof(unsigned short),1,fptr) != 1)
                    return false;
            }
        }

        // Define smooth group (this whole surface is one smooth group)
        id = TRI_SMOOTH;
        if (!fffWriteChunkHeader_local(fptr,id,smoothGrpChunkSize))
            return false;
        unsigned long smoothGrp = objId+1;
        for (ii=0; ii<totalTris; ++ii)
        {
            if (fwrite(&smoothGrp,sizeof(unsigned long),1,fptr) != 1)
                return false;
        }

        // Texture mapping info.
        string textureFile = model.getTextureFilename(objId);
        map<string,string>::const_iterator mapItr =
            textureInfo.find(textureFile);
        if (mapItr != textureInfo.end())
        {
            id = TRI_MAT_GROUP;
            long triMatChunkSize = 6 + long(mapItr->second.length()) + 1
                                   + sizeof(unsigned short)*(totalTris+1);
            if (!fffWriteChunkHeader_local(fptr,id,triMatChunkSize))
                return false;

            if (!fffWrite3dsString_local(fptr,mapItr->second))
                return false;

            if (fwrite(&totalTris,sizeof(unsigned short),1,fptr) != 1)
                return false;

            vector<unsigned short> fList;
            fList.resize(totalTris);
            for (ii=0; ii<totalTris; ++ii)
                fList[ii] = ii;

            if (fwrite(&fList[0],sizeof(unsigned short),totalTris,fptr)
                    != totalTris)
                return false;

            triChunkSize += triMatChunkSize;

            if (!fffUpdateChunkSizeInfo_local(fptr,triChunkSize,
                                              triChunkStartPos))
                return false;
        }

        chunkSize += triChunkSize;
    }

    // Correct the chunk size info for TRI_OBJECT chunk
    if (!fffUpdateChunkSizeInfo_local(fptr,chunkSize,chunkStartPos))
        return false;

    return true;
}
//****************************************************************************
//                              saveXsiFile
//****************************************************************************
static bool saveXsiFile(

    const FgString                  &fname,
    const FffMultiObjectC           &model,
    const vector<FffMultiObjectC>   *morphTargets,
    const string                    &appName)
{
    size_t numTargets=0;
    if (morphTargets)
    {
        numTargets = morphTargets->size();
    }

    FgPath      path(fname);
    path.ext = "xsi";
    FgOfstream ofs(path.str());
    if (!ofs)
    {
        return false;
    }

    //
    // Get object's bounding box (for camera and lighting info)
    //
    FgVect3F centroid(0.0f,0.0f,0.0f);
    FgVect3F lowBound(0.0f,0.0f,0.0f);
    FgVect3F hiBound(0.0f,0.0f,0.0f);
    unsigned long numTotalVtx=0;
    for (unsigned long yy=0; yy<model.numObjs(); ++yy)
    {
        // Get aliase names for all the lists.
        const vector<FgVect3F>    &vtxList = model.getPtList(yy);

        // Calculate the info
        for (unsigned long pt=0; pt<vtxList.size(); ++pt)
        {
            centroid += vtxList[pt];
            ++numTotalVtx;
            if (yy==0 && pt==0)
            {
                lowBound = vtxList[pt];
                hiBound = vtxList[pt];
            }
            else
            {
                if (lowBound[0] > vtxList[pt][0]) lowBound[0]=vtxList[pt][0];
                if (lowBound[1] > vtxList[pt][1]) lowBound[1]=vtxList[pt][1];
                if (lowBound[2] > vtxList[pt][2]) lowBound[2]=vtxList[pt][2];
                if (hiBound[0] < vtxList[pt][0]) hiBound[0]=vtxList[pt][0];
                if (hiBound[1] < vtxList[pt][1]) hiBound[1]=vtxList[pt][1];
                if (hiBound[2] < vtxList[pt][2]) hiBound[2]=vtxList[pt][2];
            }
        }
    }
    centroid /= float(numTotalVtx);
    FgVect3F camPos = centroid;
    float zdiff = hiBound[2] - lowBound[2];
    camPos[2] += zdiff*2.0f;
    float camNearPlane = 0.1f;
    float camFarPlane = zdiff*5.0f;

    //
    // Write (version 3.0) file header
    //
    ofs << "xsi 0300txt 0032\n\n" << flush;

    //
    // Output some standard info
    //
    ofs << "\n";
    ofs << "SI_FileInfo\n";
    ofs << "{\n";
    ofs << "   \"\",\n";
    ofs << "   \"" << appName << "\",\n";
    ofs << "}\n";
    ofs << "\n";
    ofs << "SI_Scene scene1\n";
    ofs << "{ \n";
    ofs << "   \"FRAMES\",\n";
    ofs << "   1.000000,\n";
    if (numTargets)
        ofs << "   " << numTargets+1 << ".000000,\n";
    else
        ofs << "   100.000000,\n";
    ofs << "   30.0,\n";
    ofs << "}\n";
    ofs << "\n";
    ofs << "SI_CoordinateSystem coord1\n";
    ofs << "{\n";
    ofs << "   1;\n";
    ofs << "   0;\n";
    ofs << "   1;\n";
    ofs << "   0;\n";
    ofs << "   2;\n";
    ofs << "   5;\n";
    ofs << "}\n";
    ofs << "\n";
    ofs << "SI_Angle\n";
    ofs << "{\n";
    ofs << "   0;\n";
    ofs << "}\n";
    ofs << "\n";
    ofs << "SI_Camera camera1\n";
    ofs << "{\n";
                // Position
    ofs << "   " << floatToString(camPos[0]) << "; " 
                 << floatToString(camPos[1]) << "; " 
                 << floatToString(camPos[2]) << ";;\n";
                // Point of interest (camera direction)
    ofs << "   " << floatToString(centroid[0]) << "; "
                 << floatToString(centroid[1]) << "; "
                 << floatToString(centroid[2]) << ";;\n";
                // Roll
    ofs << "   0.0;\n";
                // Field of view
    ofs << "   55.0;\n";
                // Near plane units
    ofs << "   " << floatToString(camNearPlane) << ";\n";
                // Far plane units
    ofs << "   " << floatToString(camFarPlane) << ";\n";
    ofs << "}\n";
    ofs << "\n" << flush;
    ofs << "SI_Ambience\n";
    ofs << "{\n";
    ofs << "   0.2; 0.2; 0.2;;\n";
    ofs << "}\n";
    ofs << "\n" << flush;
    ofs << "SI_Light light1\n";
    ofs << "{\n";
                // Type
    ofs << "   0;\n";
                // Colour of light
    ofs << "   1.0; 1.0; 1.0;;\n";
                // Position of light (above the camera)
    ofs << "   " << floatToString(camPos[0]) << "; "
                 << floatToString(hiBound[1]) << "; "
                 << floatToString(camPos[2]) << ";;\n";
    ofs << "}\n";
    ofs << "\n" << flush;


    //
    // Now output the material list.
    //
    ofs << "SI_MaterialLibrary MATLIB-scene1\n";
    ofs << "{\n";
    ofs << "   " << model.numObjs() << ",\n";
    unsigned long xx;
    for (xx=0; xx<model.numObjs(); ++xx)
    {
        string objName = model.getModelName(xx);

        ofs << "\n";
        ofs << "   SI_Material " << objName << "\n";
        ofs << "   {\n";
        ofs << "      0.7, 0.7, 0.7, 1.0,\n";   // Diffuse colour
        ofs << "      50.0,\n";                 // Specular decay
        ofs << "      1.0, 1.0, 1.0,\n";        // Specular colour
        ofs << "      0.0, 0.0, 0.0,\n";        // Emissive colour
        ofs << "      2,\n";                    // Shading type (2=phong)
        ofs << "      0.3, 0.3, 0.3,\n";        // Ambient colour

        // Now output texture image info.
        string txtFname = model.getTextureFilename(xx);
        unsigned long imgWd=0, imgHgt=0;
        if (txtFname != "" && txtFname.length() > 0)
        {
            // Get the image size by loading the image
            FgImgRgbaUb     tmpImg;
            fgLoadImgAnyFormat(path.dir()+txtFname,tmpImg);
            {
                imgWd = tmpImg.width();
                imgHgt = tmpImg.height();
            }
        }

        if (imgWd != 0 && imgHgt != 0)
        {
            string objTexName = objName + string(".Material.") 
                              + objName + string(".texture.map");
            ofs << "      SI_Texture2D " << objTexName << "\n";
            ofs << "      {\n";
            ofs << "         \"" << txtFname << "\";\n";
            ofs << "         3;\n";             // UV map (unwrapped)
            ofs << "         " << imgWd << ";" << imgHgt << ";\n";
            ofs << "         0;" << imgWd-1 << ";0;" << imgHgt-1 << ";\n";
            ofs << "         0;\n";             // No UV swap
            ofs << "         1;1;\n";
            ofs << "         0;0;\n";
            ofs << "         1.0;1.0;\n";       // UV scale
            ofs << "         0.0;0.0;\n";       // UV offset
            ofs << "         1.0,0.0,0.0,0.0,\n";   // Project matrix
            ofs << "         0.0,1.0,0.0,0.0,\n";
            ofs << "         0.0,0.0,1.0,0.0,\n";
            ofs << "         0.0,0.0,0.0,1.0;;\n";
            ofs << "         3;\n";             // No mask blending
            ofs << "         1.0;\n";           // Blending value
            ofs << "         0.0;\n";           // Texture ambient
            ofs << "         0.0;\n";           // Texture diffuse
            ofs << "         0.0;\n";           // Texture specular
            ofs << "         0.0;\n";           // Texture transparency
            ofs << "         0.0;\n";           // Texture reflectivity
            ofs << "         0.0;\n";           // Texture roughness
            ofs << "      }\n";
        }

        ofs << "   }\n" << flush;
    }
    ofs << "}\n";
    ofs << "\n" << flush;

    //
    // Now we output the models
    //
    for (xx=0; xx<model.numObjs(); ++xx)
    {
        string objName = model.getModelName(xx);

        // Get aliase names for all the lists.
        const vector<FgVect3F>    &vtxList = model.getPtList(xx);
        const vector<FgVect3UI>    &triList = model.getTriList(xx);
        const vector<FgVect4UI>    &quadList = model.getQuadList(xx);
        const vector<FgVect2F>    &txtList = model.getTextCoord(xx);
        const vector<FgVect3UI>    &txtTriList = model.getTexTriList(xx);
        const vector<FgVect4UI>    &txtQuadList = model.getTexQuadList(xx);
        bool perFacet = false;
        bool perVertex = false;
        if (vtxList.size() == txtList.size() &&
            txtTriList.size() == 0 &&
            txtQuadList.size() == 0)
            perVertex = true;
        else if (txtList.size() > 0 &&
                 txtTriList.size() == triList.size() &&
                 txtQuadList.size() == quadList.size())
            perFacet = true;

        // Create a new list for vertex normals
        vector<FgVect3F> normList;
        normList.resize(vtxList.size());

        // Output the current model
        ofs << "SI_Model MDL-" << objName << endl;
        ofs << "{\n";
        ofs << "   SI_Transform SRT-" << objName << endl;
        ofs << "   {\n";
        ofs << "      1.0, 1.0, 1.0, \n";
        ofs << "      0.0, 0.0, 0.0, \n";
        ofs << "      0.0, 0.0, 0.0, \n";
        ofs << "   }\n";
        ofs << "\n";
        ofs << "   SI_GlobalMaterial\n";
        ofs << "   {\n";
        ofs << "      \"" << objName << "\",\n";
        ofs << "      \"BRANCH\",\n";
        ofs << "   }\n";
        ofs << "\n";
        ofs << "   SI_Visibility\n";
        ofs << "   {\n";
        ofs << "      1, \n";
        ofs << "   }\n";
        ofs << "\n";
        ofs << "   SI_Mesh MSH-" << objName << endl;
        ofs << "   {\n";

            // Coordinates (position, normal, and UV)
        ofs << "      SI_Shape SHP-" << objName << "-ORG\n";
        ofs << "      {\n";
        if (perVertex || perFacet)
            ofs << "         3,\n";
        else
            ofs << "         2,\n";
        ofs << "         \"ORDERED\",\n";
        ofs << "\n";
        ofs << "         " << vtxList.size() << ",\n";
        ofs << "         \"POSITION\",\n";
        unsigned long pt;
        for (pt=0; pt<vtxList.size(); ++pt)
        {
            ofs << "         "
                << floatToString(vtxList[pt][0]) << ", "
                << floatToString(vtxList[pt][1]) << ", "
                << floatToString(vtxList[pt][2]) << ",\n";

            // Initialize the normal list to zero.
            normList[pt] = FgVect3F(0);
        }
        ofs << "\n";
        // Calculate the normal first
        for (unsigned long tt=0; tt<triList.size(); ++tt)
        {
            FgVect3F norm = calTriNormals(vtxList,triList,tt);
            normList[ triList[tt][0] ] += norm;
            normList[ triList[tt][1] ] += norm;
            normList[ triList[tt][2] ] += norm;
        }
        for (unsigned long qq=0; qq<quadList.size(); ++qq)
        {
            FgVect3F norm = calQuadNormals(vtxList,quadList,qq);
            normList[ quadList[qq][0] ] += norm;
            normList[ quadList[qq][1] ] += norm;
            normList[ quadList[qq][2] ] += norm;
            normList[ quadList[qq][3] ] += norm;
        }
        ofs << "         " << normList.size() << ",\n";
        ofs << "         \"NORMAL\",\n";
        for (pt=0; pt<normList.size(); ++pt)
        {
            float ln = normList[pt].length();
            if (ln > 0.0f) normList[pt] /= ln;
            ofs << "         "
                << floatToString(normList[pt][0]) << ", "
                << floatToString(normList[pt][1]) << ", "
                << floatToString(normList[pt][2]) << ",\n";
        }
        if (perVertex || perFacet)
        {
            ofs << "\n";
            ofs << "         " << txtList.size() << ",\n";
            ofs << "         \"TEX_COORD_UV\",\n";
            for (pt=0; pt<txtList.size(); ++pt)
            {
                ofs << "         "
                    << floatToString(txtList[pt][0]) << ", "
                    << floatToString(txtList[pt][1]) << ", \n";
            }
        }
        ofs << "      }\n" << flush;

            //
            // Facets
            //
        if (triList.size())
        {
            ofs << "\n";
            ofs << "      SI_TriangleList " << objName << "\n";
            ofs << "      {\n";
            ofs << "         " << triList.size() << ",\n";
            if (perVertex || perFacet)
                ofs << "         \"NORMAL|TEX_COORD_UV\",\n";
            else
                ofs << "         \"NORMAL\",\n";
            ofs << "         \"" << objName << "\",\n";

            // Triangle vertex id.
            ofs << "\n";
            unsigned long tri;
            for (tri=0; tri<triList.size(); ++tri)
            {
                ofs << "         "
                    << triList[tri][0] << ", "
                    << triList[tri][1] << ", "
                    << triList[tri][2] << ", \n";
            }

            // Triangle's normal id.
            ofs << "\n";
            for (tri=0; tri<triList.size(); ++tri)
            {
                ofs << "         "
                    << triList[tri][0] << ", "
                    << triList[tri][1] << ", "
                    << triList[tri][2] << ", \n";
            }

            // Triangle's texture coordinate id.
            if (perVertex)
            {
                ofs << "\n";
                for (tri=0; tri<triList.size(); ++tri)
                {
                    ofs << "         "
                        << triList[tri][0] << ", "
                        << triList[tri][1] << ", "
                        << triList[tri][2] << ", \n";
                }
            }
            else if (perFacet)
            {
                ofs << "\n";
                for (tri=0; tri<txtTriList.size(); ++tri)
                {
                    ofs << "         "
                        << txtTriList[tri][0] << ", "
                        << txtTriList[tri][1] << ", "
                        << txtTriList[tri][2] << ", \n";
                }
            }
            ofs << "      }\n" << flush;
        }

        if (quadList.size())
        {
            ofs << "\n";
            ofs << "      SI_PolygonList " << objName << "\n";
            ofs << "      {\n";
            ofs << "         " << quadList.size() << ",\n";
            if (perVertex || perFacet)
                ofs << "         \"NORMAL|TEX_COORD_UV\",\n";
            else
                ofs << "         \"NORMAL\",\n";
            ofs << "         \"" << objName << "\",\n";
            ofs << "         " << 4*quadList.size() << ",\n";
            unsigned long qu;
            for (qu=0; qu<quadList.size(); ++qu)
            {
                ofs << "         4,\n";
            }

            // Quad's vertex id.
            ofs << "\n";
            for (qu=0; qu<quadList.size(); ++qu)
            {
                ofs << "         "
                    << quadList[qu][0] << ", "
                    << quadList[qu][1] << ", "
                    << quadList[qu][2] << ", "
                    << quadList[qu][3] << ", \n";
            }

            // Quad's normal id.
            ofs << "\n";
            for (qu=0; qu<quadList.size(); ++qu)
            {
                ofs << "         "
                    << quadList[qu][0] << ", "
                    << quadList[qu][1] << ", "
                    << quadList[qu][2] << ", "
                    << quadList[qu][3] << ", \n";
            }

            // Quad's texture coordinate id.
            if (perVertex)
            {
                ofs << "\n";
                for (qu=0; qu<quadList.size(); ++qu)
                {
                    ofs << "         "
                        << quadList[qu][0] << ", "
                        << quadList[qu][1] << ", "
                        << quadList[qu][2] << ", "
                        << quadList[qu][3] << ", \n";
                }
            }
            else if (perFacet)
            {
                ofs << "\n";
                for (qu=0; qu<txtQuadList.size(); ++qu)
                {
                    ofs << "         "
                        << txtQuadList[qu][0] << ", "
                        << txtQuadList[qu][1] << ", "
                        << txtQuadList[qu][2] << ", "
                        << txtQuadList[qu][3] << ", \n";
                }
            }
            ofs << "      }\n" << flush;
        }

        // Output morph targets as animations
        if (morphTargets)
        {
            size_t numMorphs = morphTargets->size();
            if (numMorphs)
            {
                ofs << "\n";
                ofs << "      SI_ShapeAnimation SHPANIM-" << objName << endl;
                ofs << "      {\n";
                ofs << "         \"LINEAR\",\n";
                ofs << "         " << numMorphs+1 << ",\n";
                for (unsigned long mm=0; mm<numMorphs+1; ++mm)
                {
                    const vector<FgVect3F> *mvtxList = &vtxList;
                    if (mm > 0)
                        mvtxList = &((*morphTargets)[mm-1].getPtList(xx));

                    normList.resize( mvtxList->size() );

                    ofs << "\n";
                    ofs << "         SI_Shape SHP-" << objName 
                                                    << "-" << mm << "\n";
                    ofs << "         {\n";
                    if (perVertex || perFacet)
                        ofs << "            3,\n";
                    else
                        ofs << "            2,\n";
                    ofs << "            \"INDEXED\",\n";
 
                    // New vertex list.
                    ofs << "\n";
                    ofs << "            " << mvtxList->size() << ",\n";
                    ofs << "            \"POSITION\",\n";
                    for (pt=0; pt<mvtxList->size(); ++pt)
                    {
                        ofs << "            " << pt << ", "
                            << floatToString((*mvtxList)[pt][0]) << ", "
                            << floatToString((*mvtxList)[pt][1]) << ", "
                            << floatToString((*mvtxList)[pt][2]) << ",\n";

                        // Initialize the normal list to zero.
                        normList[pt] = FgVect3F(0.0f);
                    }

                    // New normal list.
                    size_t  tt;
                    for (tt=0; tt<triList.size(); ++tt)
                    {
                        FgVect3F norm=calTriNormals(*mvtxList,triList,ulong(tt));
                        normList[ triList[tt][0] ] += norm;
                        normList[ triList[tt][1] ] += norm;
                        normList[ triList[tt][2] ] += norm;
                    }
                    size_t  qq;
                    for (qq=0; qq<quadList.size(); ++qq)
                    {
                        FgVect3F norm=calQuadNormals(*mvtxList,quadList,ulong(qq));
                        normList[ quadList[qq][0] ] += norm;
                        normList[ quadList[qq][1] ] += norm;
                        normList[ quadList[qq][2] ] += norm;
                        normList[ quadList[qq][3] ] += norm;
                    }
                    ofs << "\n";
                    ofs << "            " << normList.size() << ",\n";
                    ofs << "            \"NORMAL\",\n";
                    for (pt=0; pt<normList.size(); ++pt)
                    {
                        float ln = normList[pt].length();
                        if (ln > 0.0f) normList[pt] /= ln;
                        ofs << "            " << pt << ", "
                            << floatToString(normList[pt][0]) << ", "
                            << floatToString(normList[pt][1]) << ", "
                            << floatToString(normList[pt][2]) << ",\n";
                    }

                    // Texture UV
                    if (perVertex || perFacet)
                    {
                        ofs << "\n";
                        ofs << "            " << txtList.size() << ",\n";
                        ofs << "            \"TEX_COORD_UV\",\n";
                        for (pt=0; pt<txtList.size(); ++pt)
                        {
                            ofs << "            " << pt << ", "
                                << floatToString(txtList[pt][0]) << ", "
                                << floatToString(txtList[pt][1]) << ", \n";
                        }
                    }

                    ofs << "         }\n" << flush;
                }
                ofs << "\n";
                ofs << "         SI_FCurve " << objName << "-SHPANIM-1\n";
                ofs << "         {\n";
                ofs << "            \"" << objName << "\",\n";
                ofs << "            \"SHPANIM-1\",\n";
                ofs << "            \"LINEAR\",\n";
                ofs << "            1,1,\n";
                ofs << "            " << numMorphs+1 << ",\n";
                size_t      mm;
                for (mm=0; mm<numMorphs+1; ++mm)
                {
                    ofs << "            " << mm+1 << ", " << mm << ".0,\n";
                }
                ofs << "         }\n";
                ofs << "      }\n" << flush;
            }
        }

        // End of SI_Mesh
        ofs << "   }\n";

        // End of SI_Model
        ofs << "}\n";

        // End of current model
        ofs << "\n" << flush;
    }

    //
    // Done.
    //
    if (ofs.fail())
    {
        ofs.close();
        return false;
    }
    else
    {
        ofs.close();
        return true;
    }
}
//****************************************************************************
// Local                fffWriteMdataChunk_local
//****************************************************************************
static bool fffWriteMdataChunk_local(

    FILE *fptr,
    long &chunkSize,
    const FffMultiObjectC &model)
{
    long chunkStartPos = ftell(fptr);
    unsigned short id = MDATA;
    chunkSize = 6;
    if (!fffWriteChunkHeader_local(fptr,id,chunkSize))
        return false;

    // Build Texture info
    map<string,string> textureInfoMap;  // Key = fname, map = mapName
    vector< string > mapNameList;
    for (unsigned long objId=0; objId < model.numObjs(); ++objId)
    {
        string textureFile = model.getTextureFilename(objId);
        if (textureFile != "")
        {
            // Make the filename in DOS 8.3 format
            FgPath  upath(textureFile);
            string path=upath.dir().m_str, root=upath.base.m_str, suff=upath.ext.m_str;
            if (root.length() > 8)
            {
                textureFile = path + root.substr(0,8) + "." + suff;
            }
            if (textureInfoMap.find(textureFile) == textureInfoMap.end())
            {
                textureInfoMap[textureFile] =
                    fffMdlNameTo3dsName(model.getModelName(objId),
                                        mapNameList);
            }
        }
    }

    for (map<string,string>::const_iterator itr = textureInfoMap.begin();
            itr != textureInfoMap.end(); ++itr)
    {
        long matEntryChunkStartPos = ftell(fptr);
        id = MAT_ENTRY;
        long matEntryChunkSize=6;
        if (!fffWriteChunkHeader_local(fptr,id,matEntryChunkSize))
            return false;

        id = MAT_NAME;

        long mapNameChunkSize = 6 + long(itr->second.length()) + 1;
        if (!fffWriteChunkHeader_local(fptr,id,mapNameChunkSize))
            return false;
        if (!fffWrite3dsString_local(fptr,itr->second))
            return false;

        matEntryChunkSize += mapNameChunkSize;

        long colourChunkSize = 6 + 3;

        id = MAT_AMBIENT;
        long mapAmbientColourChunkSize = 6 + colourChunkSize;
        if (!fffWriteChunkHeader_local(fptr,id,mapAmbientColourChunkSize))
            return false;
        id = COLOUR_RGB_BYTE;
        if (!fffWriteChunkHeader_local(fptr,id,colourChunkSize))
            return false;
        char red=(char)0;
        char green=(char)0;
        char blue=(char)0;
        if (fwrite(&red,1,1,fptr) != 1)
            return false;
        if (fwrite(&green,1,1,fptr) != 1)
            return false;
        if (fwrite(&blue,1,1,fptr) != 1)
            return false;

        matEntryChunkSize += mapAmbientColourChunkSize;

        id = MAT_DIFFUSE;
        long mapDiffuseColourChunkSize = 6 + colourChunkSize;
        if (!fffWriteChunkHeader_local(fptr,id,mapDiffuseColourChunkSize))
            return false;
        id = COLOUR_RGB_BYTE;
        if (!fffWriteChunkHeader_local(fptr,id,colourChunkSize))
            return false;
        red=(char)255;
        green=(char)255;
        blue=(char)255;
        if (fwrite(&red,1,1,fptr) != 1)
            return false;
        if (fwrite(&green,1,1,fptr) != 1)
            return false;
        if (fwrite(&blue,1,1,fptr) != 1)
            return false;

        matEntryChunkSize += mapDiffuseColourChunkSize;

        id = MAT_SPECULAR;
        long mapSpecularColourChunkSize = 6 + colourChunkSize;
        if (!fffWriteChunkHeader_local(fptr,id,mapSpecularColourChunkSize))
            return false;
        id = COLOUR_RGB_BYTE;
        if (!fffWriteChunkHeader_local(fptr,id,colourChunkSize))
            return false;
        red=(char)0;
        green=(char)0;
        blue=(char)0;
        if (fwrite(&red,1,1,fptr) != 1)
            return false;
        if (fwrite(&green,1,1,fptr) != 1)
            return false;
        if (fwrite(&blue,1,1,fptr) != 1)
            return false;

        matEntryChunkSize += mapSpecularColourChunkSize;

        id = MAT_TEXMAP;
        long mapFilenameChunkSize = 6 + long(itr->first.length()) + 1;
        long textMapChunkSize = 6 + mapFilenameChunkSize;
        if (!fffWriteChunkHeader_local(fptr,id,textMapChunkSize))
            return false;
        id = MAT_MAP_FNAME;
        if (!fffWriteChunkHeader_local(fptr,id,mapFilenameChunkSize))
            return false;
        if (!fffWrite3dsString_local(fptr,itr->first))
            return false;

        matEntryChunkSize += textMapChunkSize;

        if (!fffUpdateChunkSizeInfo_local(fptr,matEntryChunkSize,
                                          matEntryChunkStartPos))
            return false;

        chunkSize += matEntryChunkSize;
    }

    vector< string > objNameList;
    ulong objId;
    for (objId=0; objId<model.numObjs(); ++objId)
    {
        long objChunkStartPos = ftell(fptr);
        id = OBJECT;
        long    objChunkSize = 6;
        if (!fffWriteChunkHeader_local(fptr,id,objChunkSize))
            return false;

        string objName = fffMdlNameTo3dsName(model.getModelName(objId),
                                             objNameList);
        if (!fffWrite3dsString_local(fptr,objName))
            return false;
        objChunkSize += long(objName.length())+1;

        long triObjChunkSize;
        if (!fffWriteTriObjectChunk_local(fptr,triObjChunkSize,
                                          textureInfoMap,objId,model))
            return false;
        objChunkSize += triObjChunkSize;

        if (!fffUpdateChunkSizeInfo_local(fptr,objChunkSize,objChunkStartPos))
            return false;

        chunkSize += objChunkSize;
    }

    if (!fffUpdateChunkSizeInfo_local(fptr,chunkSize,chunkStartPos))
        return false;

    return true;
}