関数の作成 その1
main関数
今回のサンプルでは、OpenGLを使って描画を行いますが、そちらの方はできるだけシンプルな処理でいいようにGLUTを使います。
int main(int argc, char *argv[]) { // GLUT 関連 glutInit( &argc, argv ); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH); glutInitWindowSize(SCREENX, SCREENY); glutCreateWindow( "Simple COLLADA Sample" ); glutDisplayFunc( display ); glEnable(GL_TEXTURE_2D); glEnable( GL_DEPTH ); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // COLLADA関連の初期化 if(!InitCOLLADA()) return 0; glutMainLoop(); // 終了処理 cleanupCOLLADA(); return 0; }
メインループに入る前に、COLLADA DOMを使ってファイルの読み込みなどを行うInitCOLLADA()関数を呼び出しています。メインループが終わった後の終了処理をcleanupCOLLADA()にまとめてあります。
InitCOLLADA関数
// COLLADA関連の初期化 bool InitCOLLADA() { int iRet = 0; // COLLADA ドキュメントのロード g_dae = new DAE(); iRet = g_dae->load("file:/mushroom_durer.dae"); if(iRet!=DAE_OK) return false; // ジオメトリ数の取得 g_iGeometryElementCount = g_dae->getDatabase()->getElementCount(NULL, "geometry", NULL); // Triangles以外だったらTriangles化 for(int currentGeometry=0; currentGeometry<g_iGeometryElementCount; currentGeometry++) { // 現在のジオメトリの取得 domGeometry *thisGeometry; g_dae->getDatabase()->getElement((daeElement**)&thisGeometry, currentGeometry, NULL, "geometry"); // メッシュの取得 domMesh *thisMesh = thisGeometry->getMesh(); // Polylistの場合 int polylistElemntCount = (int)(thisMesh->getPolylist_array().getCount()); for(int currentPolylist=0; currentPolylist<polylistElemntCount; currentPolylist++) { domPolylist* thisPolylist = thisMesh->getPolylist_array().get(0); ConvertPolylistToTriangles( thisMesh, thisPolylist ); } // Polygonsの場合 int polygonsElementCount = (int)(thisMesh->getPolygons_array().getCount()); for(int currentPolygons=0; currentPolygons<polygonsElementCount; currentPolygons++) { domPolygons* thisPolygons = thisMesh->getPolygons_array().get(0); ConvertPolygonsToTriangles( thisMesh, thisPolygons ); } } // テクスチャ数の取得 g_iImageElementCount = g_dae->getDatabase()->getElementCount(NULL, "image", NULL); for(int i=0;i<g_iImageElementCount;i++) { g_Texture.resize( g_Texture.size()+1); g_Texture[i] = new CTGATextureHelper; // Imageの取得 domImage* thisImage; g_dae->getDatabase()->getElement((daeElement**)&thisImage, i, NULL, "image"); // ファイル名の取得 daeString name = thisImage->getInit_from()->getValue().getFile(); g_Texture[i]->LoadTGA( (char*)name ); } // テクスチャ生成 for(unsigned int i=0;i<g_Texture.size();i++) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); g_TextureObject.push_back( tex ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, g_Texture[i]->GetWidth(), g_Texture[i]->GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, g_Texture[i]->GetImage() ); } return true; }
ここでは、*.daeファイルの読み込みを行います。
DAEクラスのload()関数で、COLLADAドキュメントのロードを行います。このとき、引数はファイルパスではなくURIで指定します。
今回のサンプルでは、読み込みに成功したら最初にジオメトリ数を取得します。ジオメトリ数が取得できたらメッシュ(domMeshクラス)の取得をします。このクラスにジオメトリデータが入っています。
メッシュが取得できたら、ジオメトリデータを順番にチェックしてプリミティブタイプを確認します。プリミティブタイプは、点、線、三角形、四角形といった形状(実際には、形状だけでなくデータ構造がリストなのかストリップなどの違いがあります)がありますが、今回はPolylistとPolygonsの場合に、三角形化する処理を行います。OpenGLであれば、四角形プリミティブでも描画ができますが、今回のサンプルでは描画は三角形に統一します。三角形化の処理は、Convert~ToTriangles()関数で行います(後述)。
三角形化が済んだ後は、テクスチャの処理を行います。テクスチャに関しては、データベースに「Image」で問い合わせをします。イメージのデータは、domImageクラスに入っており、domImageが取得できれば、そこからテクスチャファイルパスが取得できます。今回は、サンプルで使っているデータのテクスチャがTGAと分かっているのでTGAのローダークラスに読み込みを行わせています(他の画像ファイル形式(BMPやJPEGなど)であればそれに対応したローダーを書く必要があります)。
g_Textureで読み込みができたら、OpenGLでのテクスチャ生成を行っています。
三角形化(Polylist)
// 三角形化(Polylist) void ConvertPolylistToTriangles(domMesh *thisMesh, domPolylist *thisPolylist) { // 4角形を3角形化 domTriangles *thisTriangles = (domTriangles *)thisMesh->createAndPlace("triangles"); unsigned int triangles = 0; thisTriangles->setMaterial(thisPolylist->getMaterial()); domP* p_triangles = (domP*)thisTriangles->createAndPlace("p"); for(int i=0; i<(int)(thisPolylist->getInput_array().getCount()); i++) { thisTriangles->placeElement( thisPolylist->getInput_array()[i]->clone() ); } int numberOfInputs = (int)getMaxOffset(thisPolylist->getInput_array()) + 1; int numberOfPrimitives = (int)(thisPolylist->getVcount()->getValue().getCount()); unsigned int offset = 0; for(int j = 0; j < numberOfPrimitives; j++) { int triangleCount = thisPolylist->getVcount()->getValue()[j] -2; int idx = numberOfInputs; // 三角形ごとにデータをコンバート for(int k = 0; k < triangleCount; k++) { // 頂点1 for(int l = 0; l < numberOfInputs; l++) { int a = thisPolylist->getP()->getValue()[offset + l]; float b; memcpy(&b, &a, sizeof(float)); p_triangles->getValue().append( thisPolylist->getP()->getValue()[offset + l]); } // 頂点2 for(int l = 0; l < numberOfInputs; l++) { p_triangles->getValue().append( thisPolylist->getP()->getValue()[offset + idx + l]); } // 頂点3 idx += numberOfInputs; for(int l = 0; l < numberOfInputs; l++) { p_triangles->getValue().append( thisPolylist->getP()->getValue()[offset + idx + l]); } //thisTriangles->setCount(thisTriangles->getCount()+1); triangles++; } offset += thisPolylist->getVcount()->getValue()[j] * numberOfInputs; } thisTriangles->setCount( triangles ); }
COLLADAドキュメント内でのPolylist要素は、多角形リストを表し、頂点データを格納します。ただ、真面目に多角形のリスト構造にすると効率が悪いので、インデックスを使うことで重複頂点がないようにしています(いわゆるインデックスバッファです)。
Polylistのインデックスデータの記述は下記のように記載されています。
<polylist material="Blue" count="6"> <input semantic="VERTEX" offset="0" source="#box-Vertex"/> <input semantic="NORMAL" offset="1" source="#box-Normal"/> <vcount>4 4 4 4 4 4</vcount> <p> 0 0 2 1 3 2 1 3 0 4 1 5 5 6 4 7 6 8 7 9 3 10 2 11 0 12 4 13 6 14 2 15 3 16 7 17 5 18 1 19 5 20 7 21 6 22 4 23 </p> </polylist>
<vcount></vcount>でくくられているところは、n角形のnにあたる数値が入っています。この例では、6つの4角形があることになります。
<p></p>でくくっている部分に頂点データのインデックスのリストがあります。<input>要素には、このリストのデータをどのように解釈するかが記載されています。ここでは、頂点座標と法線のインデックスだということが分かります。<p>内のインデックスデータは、頂点座標と法線のインデックスが一緒に入っていますが、これはoffsetで分離します。
この例では、2個の数値が1頂点分のインデックスとし、頂点座標がオフセット0、法線が1なので、順番に、頂点座標、法線、頂点座標、法線、頂点座標、法線……のようになります。このインデックスを元に頂点座標の配列や法線などのデータを参照します。
この関数では、多角形のインデックス構造を三角形に修正する処理を行っています。OpenGLでは、四角形の描画が可能ですが、描画ルーチンを1つにするためにこの処理を行っています。
三角形化(Polygons)
// 三角形化(Polygons) void ConvertPolygonsToTriangles(domMesh *thisMesh, domPolygons *thisPolygons) { // 3角形化 domTriangles *thisTriangles = (domTriangles *)thisMesh->createAndPlace("triangles"); thisTriangles->setCount( 0 ); thisTriangles->setMaterial(thisPolygons->getMaterial()); domP* p_triangles = (domP*)thisTriangles->createAndPlace("p"); for(int i=0; i<(int)(thisPolygons->getInput_array().getCount()); i++) { thisTriangles->placeElement( thisPolygons->getInput_array()[i]->clone() ); } int numberOfInputs = (int)getMaxOffset(thisPolygons->getInput_array()) +1; int numberOfPrimitives = (int)(thisPolygons->getP_array().getCount()); for(int j = 0; j < numberOfPrimitives; j++) { domP * thisPrimitive = thisPolygons->getP_array()[j]; int elementCount = (int)(thisPrimitive->getValue().getCount()); if((elementCount%numberOfInputs) != 0) { // cerr<<"Primitive "<<j<<" has an element count " // <<elementCount<<" not divisible by the number of // inputs "<<numberOfInputs<<"\n"; continue; } else { int triangleCount = (elementCount/numberOfInputs)-2; int idx = numberOfInputs; // 三角形にデータをコンバート for(int k = 0; k < triangleCount; k++) { // 頂点1 for(int l = 0; l < numberOfInputs; l++) { p_triangles->getValue().append( thisPrimitive->getValue()[l]); } // 頂点2 for(int l = 0; l < numberOfInputs; l++) { p_triangles->getValue().append( thisPrimitive->getValue()[idx + l]); } // 頂点3 idx += numberOfInputs; for(int l = 0; l < numberOfInputs; l++) { p_triangles->getValue().append( thisPrimitive->getValue()[idx + l]); } thisTriangles->setCount(thisTriangles->getCount()+1); } } } }
こちらも多角形なのですが、Polylistとはちょっと異なる記述です。下記に実際の記述例を掲載しました。
<polygons material="Blue" count="6"> <input semantic="VERTEX" offset="0" source="#box-Vertex"/> <input semantic="NORMAL" offset="1" source="#box-Normal"/> <p>0 0 2 1 3 2 1 3</p> <p>0 4 1 5 5 6 4 7</p> <p>6 8 7 9 3 10 2 11</p> <p>0 12 4 13 6 14 2 15</p> <p>3 16 7 17 5 18 1 19</p> <p>5 20 7 21 6 22 4 23</p> </polygons>
Polygonsでは<p></p>で1つのプリミティブを構成しています。n角形のnを判断するには<p></p>内のデータを<input>数で除算すると分かります。
PolylistとPolygonsはデータの構造と取得の仕方が異なりますが、三角形化自体の考え方はあまり変わりません。
