※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ファイルを読み込む」を参照してください。
|