在開始之前, Cogl跟ODE可能沒什麼人聽過, 先大概介紹一下
1.Introduction
Cogl:
網址: http://wiki.clutter-project.org/wiki/Cogl
Cogl是一套處理3D幾何的函式庫, 數於Open Source專案ClutterProject裡的一部份,底層使用OpenGL, OpenGLES, 目前仍在開發中, 功能尚沒有很齊全(2011/05), 另外在使用上要很注意, 使用Clutter做開發的軟體, 由於底層也使用Cogl, 如果在其中自己直接使用Cogl, 有可能會對Clutter造成影響, 像是設定viewport等等都有可能會互相影響, 混用的時候要很注意.
ODE:
網址:http://www.ode.org/
ODE是一套知名的Open Source物理引擎, 跟市面上現有的物理引擎比較起來功能雖然不強大, 但基礎功能該有的都有, 沒有很要求物理處理的效能以及功能的話用起來還不錯.
2.Implementation
先來看結果的影片,處理拼圖塊的方式有很多種, 可以利用2d alpha map的方式, 產生無厚度的拼圖塊, 也可以直接做出3d model, 寫讀檔程式自己繪圖, 在這邊我用的是自己建3d model的方式, 以下做論述
結果影片
.puzzle model/texture
在puzzle的model部分, 我的方式是自己建模並輸出.obj檔案, 並在程式中寫讀檔以及繪圖, 在這邊有個重點是貼圖座標一定要處理好, 之後在程式中繪圖處理才比較不會太大的問題.
.建立3D模型
如圖, 把3ds_max開起來, 乖乖的慢慢拉點線面, 建好後輸出obj檔案, 在這邊說明一下, 在選擇輸出格式時要注意選擇有含貼圖資訊的格式, 並不是所有格式都有含貼圖資訊.....
建立3D模型-網格
建立3D模型-貼圖
.軟體實作
- 初始化
這邊為了方便, 使用clutter來處理介面,
clutter初始化
ClutterActor *stage ; //clutter初始化 clutter_init (&argc, &argv); //創建clutter基礎元件, 後續基本元件(actor)會加在stage裡 stage = clutter_stage_new();
ODE初始化
//dInitODE舊版ode不需要使用, 新版有些功能需要先呼叫才能使用, 如joint, collision等等 dInitODE(); //ode基礎的元件, 所有的物件產生在world下面 mWorld = dWorldCreate(); //建立一個碰撞空間, ode有幾種空間, dSimpleSpaceCreate較適合小量物件的碰撞 mSpace = dSimpleSpaceCreate(0); //建立儲存接觸點的群組 mContactgroup = dJointGroupCreate(0); //為了不讓物體一直掉到下去, 建立個地板, 參數為ax+by+cz=d的a,b,c,d dCreatePlane(mSpace, 0, -1, 0, -10); //設定重力 dWorldSetGravity(mWorld, 0, 1.0, 0); //設定error correcting及constraint force mixing dWorldSetERP(mWorld, 0.2); dWorldSetCFM(mWorld, 1e-5);
- 註冊訊息事件
註冊訊息事件
//註冊繪圖事件, 最主要的處理都在這 g_signal_connect_after (actor, "paint", G_CALLBACK (cogl_paint_cb), &gFrameData); //destroy, 裡面處理釋放資源 g_signal_connect (actor, "destroy", G_CALLBACK (actor_destroy), NULL); //滑鼠事件 g_signal_connect (actor, "button-press-event", G_CALLBACK (button_press_cb), NULL); g_signal_connect (actor, "button-release-event", G_CALLBACK (button_release_cb), NULL); g_signal_connect (actor, "motion-event", G_CALLBACK (mouse_cb), NULL);
- 繪圖與物理處理(paint事件)
先處理Cogl的3d model繪圖, obj檔案的讀取部分可以在網路上找到現成的code, 由於是不是我自己寫的, 此部分就略掉不講, 假設目前已經把3d點座標與貼圖座標都讀取到陣列存放, 以下是主要的程式碼
Cogl Load 3d puzzle model
void PuzzleModel::LoadWithMesh(string modelFilePath,Vector3D position) { float maxx,maxy,maxz; float minx,miny,minz; maxx=maxy=maxz= -999999; minx=miny=minz= 999999; mMesh = new ObjData(modelFilePath); ObjMesh *pMesh = mMesh->mObjMesh; #ifdef _DEBUG_MODEL_INFO printf("all point size : %d\n", pMesh->m_iNumberOfVertices); printf("all texture point size : %d\n", pMesh->m_iNumberOfTexCoords); printf("all normal point size : %d\n", pMesh->m_iNumberOfNormals); printf("faces size : %d\n", pMesh->m_iNumberOfFaces); #endif //1 face = 3 vertex = 3 texture = 3 normal..1面3點, 每點都有texture以及normal mDrawPointSize = pMesh->m_iNumberOfFaces*3;//pMesh->m_iNumberOfVertices; mTexturePointSize = pMesh->m_iNumberOfFaces*3;//pMesh->m_iNumberOfTexCoords; mNormalPointSize = pMesh->m_iNumberOfFaces*3;//pMesh->m_iNumberOfNormals; if( pMesh->m_aNormalArray != NULL && pMesh->m_aTexCoordArray != NULL ) { unsigned int i; unsigned int iDraw = 0,iTexture = 0,iNormal=0; mDrawPointGroup = new float[mDrawPointSize*3]; mTexturePointGroup = new float[mTexturePointSize*2]; mNormalPointGroup = new float[mNormalPointSize*3]; for(i=0;i<m_iNumberOfFaces;i++) { unsigned int j; ObjFace *pf = &pMesh->m_aFaces[i]; /* ** Draw the polygons with normals & uv co-ordinates */ for(j=0;j<3;j++) { float fx,fy,fz; fx=pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].u; fy=pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].v; mTexturePointGroup[iTexture++]=fx; mTexturePointGroup[iTexture++]=fy; fx=pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].x; fy=pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].y; fz=pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].z; mNormalPointGroup[iNormal++] = fx; mNormalPointGroup[iNormal++] = fy; mNormalPointGroup[iNormal++] = fz; fx=pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].x; fy=pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].y; fz=pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].z; mDrawPointGroup[iDraw++] = fx; mDrawPointGroup[iDraw++] = fy; mDrawPointGroup[iDraw++] = fz; if(fx>maxx) maxx=fx; if(fy>maxy) maxy=fy; if(fz>maxz) maxz=fz; if(fx<minx) minx=fx; if(fy<miny) miny=fy; if(fz<minz) minz=fz; } } #ifdef _DEBUG_MODEL_INFO //設定bounding box, 左上為原點 //右下 printf("max:%f,%f,%f\n",maxx,maxy,maxz); printf("min:%f,%f,%f\n",minx,miny,minz); #endif //為了設定物理物件的大小, 此處計算bounding box mBoundingBox[0] = Vector3D(minx,miny,minz); mBoundingBox[1] = Vector3D(maxx,maxy,maxz); //建立ode物理body, 後面說明 CreatePhysicsBody(position); //Cogl繪圖需要先創建buffer,gl_Vertex畫點, //gl_Normal設定normal, 要繪圖至少要有gl_Vertex mVBO = cogl_vertex_buffer_new (mDrawPointSize); cogl_vertex_buffer_add (mVBO, "gl_Vertex", 3, COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, sizeof (float)*3, mDrawPointGroup); cogl_vertex_buffer_add (mVBO, "gl_Normal", 3, COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, sizeof (float)*3, mNormalPointGroup); //設定texture, 此處為了方便寫死texture.png, 真的實做請自己實做一個resource manager處理可能比較適當 CoglTextureFlags flags = COGL_TEXTURE_NO_SLICING; CoglPixelFormat internalFormat = COGL_PIXEL_FORMAT_ANY; CoglHandle texture = cogl_texture_new_from_file ("texture.png",OGL_TEXTURE_NO_SLICING,COGL_PIXEL_FORMAT_ANY, NULL); if(texture != NULL) { //同樣是為了繪圖需要的buffer, 此處是畫材質用的貼圖座標 cogl_vertex_buffer_add (mVBO, "gl_MultiTexCoord0", 2, COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, sizeof (float)*2, mTexturePointGroup); mModelTexture = cogl_material_new (); cogl_material_set_layer ((CoglMaterial*)mModelTexture, 0, texture); } }
處理完model繪圖後, 接下來要替此物件建立一個物理body, 使之產生物理的效果
建立物理body
void PuzzleModel::CreatePhysicsBody(Vector3D position) { float sizex = mBoundingBox[1].mx-mBoundingBox[0].mx; float sizey = mBoundingBox[1].my-mBoundingBox[0].my; float sizez = mBoundingBox[1].mz-mBoundingBox[0].mz; mPhyObj = PhysicsManager::Singleton()->createBox(sizex,sizey,sizez); dMatrix3 R; VECTOR tempVect(0.0, 0.0, 0.0); dBodySetLinearVel(mPhyObj->mBody, tempVect.x, tempVect.y, tempVect.z); dRFromAxisAndAngle(R, 0, 1, 0, 0); dBodySetRotation(mPhyObj->mBody, R); dBodySetPosition(mPhyObj->mBody, position.mx, position.my, position.mz); #ifdef _DEBUG_MODEL_INFO //設定bounding box, 左上為原點 //右下 printf("GEOM(box) size:%f,%f,%f\n",sizex,sizey,sizez); printf("GEOM(box) pos:%f,%f,%f\n",position.mx, position.my, position.mz); #endif }
model的位移以及旋轉, 也就是其空間矩陣值由ODE來處理, 成功建立物理body後, 在paint重繪處理時一併更新ODE物理計算, 將ODE計算出來的空間矩陣設定給此物件, 透過Cogl繪出即可, 此處要注意的是ODE的空間矩陣與Cogl(同OpenGL)放置方式不一樣, 所以需要再多一個轉換步驟
Puzzle model的繪圖Update處理
void PuzzleModel::Update(float elasptime) { if(!mDrawPointGroup || mDrawPointSize <=0 ) return; cogl_push_matrix (); //scale與translate順序會影響位移大小, scale先放則移動會乘上倍率 cogl_scale (mScale.mx, mScale.my, mScale.mz); //物理處理 if(mPhyObj) { /* | 0 1 2 3 | | 0 4 8 12 | | | | | R = | 4 5 6 7 | | 1 5 9 13 | | | M = | | | 8 9 10 11 | | 2 6 10 14 | | | p = | 0 1 2 | | 3 7 11 15 | */ MATRIX geoMatrix; geoMatrix.LoadIdentity(); const dReal *pos = dGeomGetPosition(mPhyObj->mGeom); const dReal *R = dGeomGetRotation(mPhyObj->mGeom); #ifdef _DEBUG_MODEL_MATRIX printf("\n*************\n"); printf("{%f,%f,%f,%f}\n{%f,%f,%f,%f}\n{%f,%f,%f,%f}\n",R[0],R[1],R[2],R[3], R[4],R[5],R[6],R[7], R[8],R[9],R[10],R[11]); printf("\n(%f,%f,%f)\n", pos[0],pos[1],pos[2]); printf("*************\n"); #endif //arkkk 這裡一定要注意ode的dReal define是否為double精度, 否則會出問題(看ode\common.h, dDOUBLE/dSINGLE) geoMatrix.ODEtoOGL((const float*)pos, (const float*)R); #ifdef _DEBUG_MODEL_MATRIX printf("\n==============\n"); printf("{%f,%f,%f,%f}\n{%f,%f,%f,%f}\n{%f,%f,%f,%f}\n{%f,%f,%f,%f}\n",geoMatrix.Element[0],geoMatrix.Element[4],geoMatrix.Element[8],geoMatrix.Element[12], geoMatrix.Element[1],geoMatrix.Element[5],geoMatrix.Element[9],geoMatrix.Element[13], geoMatrix.Element[2],geoMatrix.Element[6],geoMatrix.Element[10],geoMatrix.Element[14], geoMatrix.Element[3],geoMatrix.Element[7],geoMatrix.Element[11],geoMatrix.Element[15]); printf("===============\n\n"); #endif CoglMatrix matrix_cogl,matrix_gl_ode,matrix_result; //gl to cogl matrix cogl_matrix_init_from_array(&matrix_gl_ode, geoMatrix.Element); //取出現在matrix cogl_get_modelview_matrix(&matrix_cogl); cogl_matrix_multiply(&matrix_result, &matrix_cogl, &matrix_gl_ode); cogl_set_modelview_matrix(&matrix_result); } else { cogl_translate (mPosition.mx, mPosition.my, mPosition.mz); cogl_rotate (mRotate.mx, 0, 0, 1); cogl_rotate (mRotate.my, 0, 1, 0); cogl_rotate (mRotate.mz, 1, 0, 0); } cogl_set_depth_test_enabled (TRUE); if(mModelTexture) cogl_set_source (mModelTexture); cogl_vertex_buffer_draw (mVBO, COGL_VERTICES_MODE_TRIANGLES, 0, mDrawPointSize); cogl_set_depth_test_enabled (FALSE); cogl_pop_matrix (); }
釋放資源也是很重要的
PuzzleModel::~PuzzleModel() { if(mMesh) { cogl_vertex_buffer_submit(mVBO); delete mMesh; mMesh = NULL; cogl_vertex_buffer_delete(mVBO,"gl_Vertex"); cogl_vertex_buffer_delete(mVBO,"gl_Normal"); } if(mModelTexture) { cogl_vertex_buffer_submit(mVBO); cogl_vertex_buffer_delete(mVBO,"gl_MultiTexCoord0"); cogl_material_ref(mModelTexture); mModelTexture = NULL; } }
model的處理到此大部分完成了, 接下來把paint重繪的部分補上
paint繪圖處理
static void cogl_paint_cb (ClutterActor *actor, FrameData *data) { cogl_set_viewport (0, 0, data->mFrameWidth, data->mFrameHeight); cogl_perspective (60, /* field of view */ 1, /* aspect ratio */ 0.1, /* distance to near z plane */ 100); /* distance to far z plane */ setup_2d_device_coordinates_modelview (data->mFrameWidth, data->mFrameHeight); cogl_clear (&gWhite, COGL_BUFFER_BIT_COLOR); //center cogl_translate(gFrameData.mFrameWidth/2, gFrameData.mFrameHeight/2, 0.0f); //注意, NearCallback函式關係到物理碰撞時的處理 dSpaceCollide(mSpace, 0, NearCallback); dWorldQuickStep(mWorld, 0.05); dJointGroupEmpty(mContactgroup); UpdateAllModels(); }
到這一步執行下去應該還是會發現各個puzzle model的碰撞並無效果, 各位應該已經發現到dSpaceCollide的NearCallback尚未處理, 最後一步來看物理body碰撞時的處理
ODE body碰撞處理
static void NearCallback(void *data, dGeomID o1, dGeomID o2) { int i; dBodyID b1 = dGeomGetBody(o1); dBodyID b2 = dGeomGetBody(o2); dContact contact[MAX_CONTACTS]; // up to MAX_CONTACTS contacts per box-box for (i = 0; i < MAX_CONTACTS; i++) { contact[i].surface.mode = dContactBounce | dContactSoftCFM; contact[i].surface.mu = dInfinity; contact[i].surface.mu2 = 0; //表面彈性, 0~1 contact[i].surface.bounce = 0.5;//0.9; //反彈速度 contact[i].surface.bounce_vel = 0.5;//0.1; contact[i].surface.soft_cfm = 0.01; } if (int numc = dCollide(o1, o2, MAX_CONTACTS, &contact[0].geom, sizeof(dContact))) { for (i = 0; i < numc; i++) { dJointID c = dJointCreateContact(mWorld, mContactgroup, contact + i); dJointAttach(c, b1, b2); } } }
3.後記
由於Cogl尚在開發中, 目前我並沒有在裡面發現有實做類似gluProject以及gluUnProject等函式, 我自己取其轉換矩陣下去計算發現滑鼠點選位置與3d空間會差一個位移, 原因我也不太確定, 導致用滑鼠點不太好讓ODE的raycast準確的打到物件, 後續有在實作再行補上點選拉動部分, 也許Cogl馬上就會有這功能, 或者其實有, 只是我沒發現吧, 另外, 以上程式碼為了只拿出重點部分, 所以是從原來架構精簡搬出來, 大錯誤應該是沒有, 不過可能會有小編譯錯誤, 以上~~
沒有留言:
張貼留言