在開始之前, 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馬上就會有這功能, 或者其實有, 只是我沒發現吧, 另外, 以上程式碼為了只拿出重點部分, 所以是從原來架構精簡搬出來, 大錯誤應該是沒有, 不過可能會有小編譯錯誤, 以上~~


沒有留言:
張貼留言