- OBJ→TLO変換 -
※TLOファイルはBrightObject version 1.2.0.0より無くなりました。


OBJとTLOの関係が分かったので実際にOBJをTLOへ変換してみます。
MTLファイルやテクスチャ画像はOBJで使用したものをそのままTLOでも使いますので変換はせず、
オブジェクトの頂点などが記録されているOBJのみを変換すればOKです。



[ 構造体 ]

//obj読み込み用:幾何形状データ(Obj形式用)
struct Obj
{
    //v
    int        num_vertices;  // 頂点数
    Vector *   vertices;      // 頂点座標配列 [num_vertices]

    //vn
    int        num_normals;
    Vector *   normals;       // 法線ベクトル配列 [num_normals]

    //vt
    int        num_tex_coords;
    Vector *   tex_coords;    // テクスチャ座標配列 [num_tex_coords]

    //f
    int        num_men;             // 全ての面の数
    int        num_part_men[20];    //usemtlごとの面の数
    int *      men_v_no;                // 各頂点の頂点座標番号配列 [num * 3]
    int *      men_vn_no;               // 各頂点の法線ベクトル番号配列 [num * 3]
    int *      men_vt_no;               // 各頂点のテクスチャ座標番号配列 [num * 3]

    //mtlファイルの情報
    char       mtlfilename[BUFFER_LENGTH];

    int        useMtlNameCnt;       //usemtlを見つけた回数
    MtlInfo    material[20];    // 使用するマテリアル名(最大20個とする)
};



[ OBJ→TLO変換関数ソース ]

//OBJファイルの形式を変換してTLOファイルに書き出す(fの順番通りに書き直す)
int TranslateOBJ(Obj *_obj, const char *filename)
{
    FILE *fp;
    int i, vj, vtj, vnj, endCnt, writeMtlIdx;
    
    i = 0;
    vj = 0;
    vtj = 0;
    vnj = 0;
    writeMtlIdx = 0;

    fp = fopen(filename, "w");
    if(fp == NULL)
    {
        return 1;
    }

    //ループに入る前の処理
    fputs("# Created by BrightObject[TLO FILE]\n\n", fp);   //ファイルの先頭に入れる文字列

    //マテリアル情報ごとの面の数
    for(i = 0; i < _obj->useMtlNameCnt; i++)
        fprintf(fp, "men_part_num %d\n", _obj->num_part_men[i]);
    
    fprintf(fp, "men_num %d\n", _obj->num_men);     //すべての面の数(callocで使えるから)
    fprintf(fp, "mtllib %s\n", _obj->mtlfilename);  //TLOのファイル名はobjファイルと一緒にする

    //TLO形式で書き出す
    for(i = 0; i < _obj->useMtlNameCnt + 1; i++)
    {
        endCnt = _obj->material[i].addIndex * 3;    //次のusemtlを書く場所までv, vt, vnをファイルに書き出す

        if(endCnt == -27)   //書き込むusemtlがなくなったら最後までやる
            endCnt = _obj->num_men * 3;

        for( ; vj < endCnt; vj++)
        {
            fprintf(fp, "v %f %f %f\n",
            		_obj->vertices[ _obj->men_v_no[vj] ].x,
            		_obj->vertices[ _obj->men_v_no[vj] ].y,
            		_obj->vertices[ _obj->men_v_no[vj] ].z);
        }
        fprintf(fp, "\n");

        for( ; vtj < endCnt; vtj++)
        {
            fprintf(fp, "vt %f %f\n",
            		_obj->tex_coords[ _obj->men_vt_no[vtj] ].x,
            		_obj->tex_coords[ _obj->men_vt_no[vtj] ].y);
        }
        fprintf(fp, "\n");

        for( ; vnj < endCnt; vnj++)
        {
            fprintf(fp, "vn %f %f %f\n",
            		_obj->normals[ _obj->men_vn_no[vnj] ].x,
            		_obj->normals[ _obj->men_vn_no[vnj] ].y,
            		_obj->normals[ _obj->men_vn_no[vnj] ].z);
        }
        fprintf(fp, "\n");

        //usemtl行を書く
        if(endCnt != _obj->num_men * 3)
            fprintf(fp, "usemtl %s\n", _obj->material[i].mtlName);
    }

    fclose(fp);
    return 0;
}


[ 解説 ]

書き込むためのファイルを作成し、OBJファイルの中の、
・適用されるマテリアル名(usemtl)で区切られた範囲内での面の数(men_part_num配列)
・OBJを構成するすべての面の数(num_men)
・MTLファイル名(mtlfilename)
を最初に書きます。なお、OBJファイルの面の構成はすべて三角面であることが前提です。
men_part_num配列はOBJファイルのをusemtl句ごとに区切った時の面の数(「頂点数 / 3」が面の数)ですので、
複数のマテリアル属性を使っている場合には、その分だけ書き込まれます。

その次がOBJファイルからTLOファイルへの変換処理です。
LoadObj関数でObj構造体のデータが作られているものとします。

最初に出てくる、

	_obj->material[i].addIndex * 3

の部分は「先頭から数えて何番目の面の箇所に"usemtl"が書かれているか」という情報が格納されています。
例えば、OBJファイルにて三角面の合計が5のオブジェクトのfのデータは、

f 256/1/1 1/2/2 960/3/3
f 1/5/5 253/6/6 960/7/7
f 253/9/9 1/10/10 823/11/11
f 1/13/13 254/14/14 823/15/15
f 254/17/17 1/18/18 757/19/19

ですね。ここに最初の面の前、2番目の面と4番目の面の後ろに"usemtl"が入るとします。

usemtl mat00
f 256/1/1 1/2/2 960/3/3
f 1/5/5 253/6/6 960/7/7
usemtl mat01
f 253/9/9 1/10/10 823/11/11
f 1/13/13 254/14/14 823/15/15
usemtl mat02
f 254/17/17 1/18/18 757/19/19

このようになります。
最初の2個のfはmat00が適用され、次の2個のfにはmat01、最後のfにはmat02が適用されます。(OBJの仕様)
この時の"usemtl"の場所を示すのがaddIndex配列です。
上記の場合だと、

addIndex[] = {0, 2, 4, -9, -9, …};

のような値が格納されます。-9はデータが無いということを示す値です。
addIndex[]の中身はLoadObj関数で次のように作成されています。

        if(strncmp(line, "usemtl", 6) == 0)
        {
            // テキストを解析
            sscanf(line, "usemtl %s", name);

            //OBJファイルの最初のusemtlから"f"の数を数え、次のusemtlが出てきたところまでの面の数を格納
            obj->material[obj->useMtlNameCnt].addIndex = obj->num_men;
            strcpy(obj->material[obj->useMtlNameCnt].mtlName, name);
            obj->useMtlNameCnt++;
        }

-9は、LoadObj関数で以下のように初期化されています。

        for(i = 0; i < 20; i++)
        {
            obj->material[i].addIndex = -9; //1つのusemtl
            obj->num_part_men[i] = 0;
        }

addIndexの値がendCnt == -27かどうかを判断することで、残りのすべてのデータをTLOファイルの終わりまで書くかどうか決まります。
最初のusemtlから数え、次のusemtlが出てきた所までの面の合計値がaddIndexに格納されていく、ということになります。
(LoadObj関数で実際に行われています)

これを踏まえて上記ソースを追っていくと分かりやすいかもしれません。
また、addIndexを3倍にしてるのは三角面のデータを扱うので、

v a0 a1 a2
v b0 b1 b2
v c0 c1 c2

で1つの三角面を表すことになるので、面の数を3倍してデータを書き込んでいます。
TLOファイルについて詳しくは前項のTLOファイル概要を参照してください。
Obj構造体にOBJファイルのデータを格納することについては「OBJファイルを読み込む」を参照してください。


inserted by FC2 system