MD2文檔讀取與解析

MD2文檔讀取與解析
MD2文檔讀取與解析
MD2文檔讀取與解析

.MD2文檔由美國id Software為其QuakeII開發3D模型.雖然它無骨架但它讀取簡單,而最重要是可以通過Internet穩到大量MD2文檔.根據模型規範.MD2格式最多含198動畫幀編號為0~197. 動畫幀將用於組成『步行』『功擊』『站立』『死亡』等動畫.MD2文檔 通常有下面幾個文檔組成:

文檔 簡述
TRIS.MD2 角色3D模型
WEAPON.MD2 武器3D模型
TRIS.PCX 角色皮膚也就是紋理圖檔,通常為8Bit『256色』長寬為256*256保存為.PCX格式
WEAPON.PCX 武器皮膚

 

MD2格式 簡述
Head 文檔頭部格式3D模型描述
Data 3D數據

 

MD2文檔頭部格式 MD2_HEADER 簡述
int id; MD2文檔標記’IDP2′,用於判斷是否MD2文檔
int version; 版本號總為8
int skin_width 皮膚紋理圖寬度.此值相對於紋理座標textcoord,通常為256
int skin_height; 皮膚紋理圖高度.此值相對於紋理座標textcoord,通常為256
int frame_size; 每幀字節總量
int skin_num; 皮膚紋理總量,忽略.
int vertex_num; 單幀頂點量.每幀字節量均相同.有些教科書搞錯左此值.
int textcoord_num; 紋理座標總量
int polygon_num; 多邊形總量
int command_num; OpenGL命令總量,忽略
int frame_num; 幀總量id softwarek規定為198幀編號為0~197.有些模型可能多於此值
int skin_offset; 皮膚pcx偏移量每個64字節.因包含QuakeII專用路徑.忽略.
int textcoord_offset; 紋理座標偏移量
int polygon_offset; 多邊形偏移量
int frame_offset; 幀偏移量
int command_offset; 命令偏移量忽略
int end_offset; 結尾偏移量.相當於文檔長度

MD2紋理座標通過(data + textcoord_offset)得到,結構如下:

typedef struct MD2_TEXTCOORD_TYP {

WORD u, v;

}MD2_TEXTCOORD,*MD2_TEXTCOORD_PTR;

 

MD2多邊形通過(data + polygon_offset)得到,保存頂點與紋理索引.結構如下:

typedef struct MD2_POLYGON_TYP {

WORD vertex_index[3];// 頂點索引

WORD textcoord_index[3];// 紋理座標索引

}MD2_POLYGON,*MD2_POLYGON_PTR;

 

MD2關鍵幀通過(data + frame_offset)得到,幀頭包含對頂點進行縮放與平移因子.頂點數組長度由vertex_num確定.結構如下:

typedef struct MD2_FRAME_TYP{

float scale[3];// 縮放

float translate[3];// 平移

char  name[16];// 動畫幀ASCII名

MD2_POINT list[1];// 頂點數組

}MD2_FRAME,*MD2_FRAME_PTR;

 

MD2頂點包含於關鍵幀裡,每個頂點由『xyz座標』與『法線索引』組成.『法線索引表』你需要自已構建.法線將在運行時計算所以忽略.結構如下:

typedef struct MD2_POINT_TYP {

BYTE v[3];// xyz頂點

BYTE noraml_index;// 頂點法線索引,此值忽略

}MD2_POINT,*MD2_POINT_PTR;

 

MD2動畫,用於控制模型動作速度,以秒為單位.結構如下:

typedef struct MD2_ANIMATION_TYP{

int   start;  // 起始幀索引

int   end;    // 結束幀索引

float irate;   // 插幀頻率(0.0f~1.0f),1.0f表示不插幀

float speed;   // 用於控制動畫播放時間,一般設為0~10秒

} MD2_ANIMATION, *MD2_ANIMATION_PTR;

動畫索引 動作名 幀索引 插幀頻率(幀) 播放時間(秒)
0 STANDING_IDLE站立 0-39 0.5f 4
1 RUN奔跑 40-45 0.5f 4
2 ATTACK開火/攻擊 46-53 0.5f 2
3 PAIN1 54-57 0.5f 4
4 PAIN2 58-61 0.5f 4
5 PAIN3 62-65 0.5f 4
6 JUMP跳躍 66-71 0.5f 5
7 FLIP手勢 72-83 0.5f 3
8 SALUTE  敬禮 84-94 0.5f 5
9 TAUNT 嘲笑 95-111 0.5f 5
10 WAVE 揮手致意 112-122 0.5f 5
11 POINT 指向它人 123-134 0.5f 5
12 CROUCH STAND   蹲伏-不動 135-153 0.5f 5
13 CROUCH WALK    蹲伏-行走 154-159 0.5f 5
14 CROUCH ATTACK  蹲伏-開火 160-168 0.5f 5
15 CROUCH_PAIN    蹲伏-被擊打 169-172 0.5f 5
16 CROUCH_DEATH  蹲伏-死亡 173-177 0.25f 5
17 DEATH_BACK    死亡-後趴 178-183 0.25f 5
18 DEATH_FORWARD 死亡-前趴 184-189 0.25f 5
19 DEATH_SLOW   緩慢死亡 190-197 0.25f 5

現在需要MD2結構用於保存3D模型數據

MD2模型數據結構 簡述
MD2_ANIMATION animation_array[20] 動畫,MD2通常有20種不同動作
int animation_num; 動畫總量
int frame_num; 幀量id softwarek規定198幀
MD2_POLYGON_PTR polygon_array; 多邊形列表
int polygon_num; 多邊形總量
VECTOR3D_PTR  vertex_array; 頂點數組 長度=幀總量*單幀頂點量
int vertex_num; 單幀頂點量
VECTOR2D_PTR textcoord_array; 紋理座標數組,只有一組,由所有幀共享
int textcoord_num; 紋理座標總量
TEXTURE texture_array[8] 紋理skin
int texture_num; 皮膚紋理圖總量
float radius_avg; 平均半徑
float radius_max; 最大半徑

在遊戲中低級單位都一至如步兵.需要單獨保存位置、面方、動畫等狀態.需要一個新結構對MD2進行封裝.以共享數據節約空間.並且為讓動畫流暢需要對關鍵幀進行插值(插入其它幀).

3D模型結構定義(MODEL3D) 簡述
int state; 狀態, 死亡/存活
int attr; 屬性
int color; 沒有紋理時使用顏色 RGB(255,255,255)
VECTOR3D pos; 位置
VECTOR3D rot; 旋轉
int anim; 動畫索引
bool  loop; 循環播放
float speed; 動畫速度,(0.0f~1.0f)數值越小速度越慢,數值越大速度越快
float frame; 當前幀索引
float irate; 插幀頻率(0.0f~1.0f),1.0f表示不插幀
float count; 插幀計數器
bool complete; 動畫完成播放標記
VECTOR3D_PTR vertex_array; 單幀頂點量
VECTOR3D_PTR normal_array; 法線數組
TEXTURE_PTR texture; 指向紋理
MD2_PTR md2; 指向MD2模型

在載入數據之前還需要定義幾個頂點控制

#define MD2_INVERT_X       0x0001// 反轉X軸

#define MD2_INVERT_Y       0x0002// 反轉Y軸

#define MD2_INVERT_Z       0x0004// 反轉Z軸

#define MD2_SWAP_XY        0x0010// 交換XY軸

#define MD2_SWAP_YZ        0x0020// 交換YZ軸

#define MD2_SWAP_XZ        0x0040// 交換XZ軸

#define MD2_INVERT_WINDING_ORDER  0x0100 // 反轉環繞順序

基本結構已定義可以讀取數據並分釋:

bool Load_Data_MD2(MD2_PTR md2, PBYTE data, int size,float scale,DWORD flag ){

1.讀取MD2頭部,data為文檔數據

MD2_HEADER_PTR    header = (MD2_HEADER_PTR)data;

2.判斷是否MD2模型

if (header->id != ‘2PDI’) // MD2文檔標記

return false;

3.判斷版本號總為8

if (header->version != 8)

return false;

4.幀總量id softwarek規定198幀

md2->frame_num = header->frame_num;

5.皮膚紋理總量設為0

md2->texture_num   = 0;

4.多邊形總量

md2->polygon_num = header->polygon_num;

5.讀取幀頂點量

md2->vertex_num = header->vertex_num;

6.分配多邊形記憶體

md2->polygon_array = (MD2_POLYGON_PTR)malloc(md2->polygon_num*sizeof(MD2_POLYGON));

7.分配頂點記憶體

md2->vertex_array = (VECTOR3D_PTR)malloc(md2->frame_num * md2->vertex_num * sizeof(VECTOR3D));

8.分配紋理座標記憶體.以繪畫三角形進行排列

md2->textcoord_array = (VECTOR2D_PTR)malloc(md2->polygon_num * 3 * sizeof(VECTOR2D));

9.遍歷每一幀

for (int findex = 0; findex < md2->frame_num; ++findex){

MD2_FRAME_PTR frame;// 讀取幀

frame = (MD2_FRAME_PTR)(data+header->frame_offset + header->frame_size * findex);

10.遍歷每一頂點

for (int vindex = 0; vindex < md2->vertex_num; ++vindex){

VECTOR3D v ;

11.對頂點座標進行縮放和平移

v.x = frame->list[vindex].v[0] * frame->scale[0] + frame->translate[0];

v.y = frame->list[vindex].v[1] * frame->scale[1] + frame->translate[1];

v.z = frame->list[vindex].v[2] * frame->scale[2] + frame->translate[2];

12.跟據傳入參數進行縮放

if (scale != NULL)

v = v * scale;

13.反轉座標軸

if (flag & MD2_INVERT_X)

v.x = -v.x;

if (flag & MD2_INVERT_Y)

v.y = -v.y;

if (flag & MD2_INVERT_Z)

v.z = -v.z;

14.交換座標軸

float temp;

if (flag & MD2_SWAP_YZ)

SWAP(v.y, v.z, temp);

if (flag & MD2_SWAP_XZ)

SWAP(v.x, v.z, temp);

if (flag & MD2_SWAP_XY)

SWAP(v.x, v.y, temp);

15.將頂點插入列表中

md2->vertex_array[findex*md2->vertex_num + vindex] = v;

}

}

16.讀取紋理座標

MD2_TEXTCOORD_PTR textcoord = (MD2_TEXTCOORD_PTR)(data + header->textcoord_offset);

17.讀取多邊形

MD2_POLYGON_PTR polygon = (MD2_POLYGON_PTR)(data+header->polygon_offset);

18.遍歷多邊形

for (int pindex = 0; pindex<header->polygon_num; ++pindex){

if (flag & MD2_INVERT_WINDING_ORDER) {

19.反轉頂點環繞順序

md2->polygon_array[pindex].vertex_index[0] = polygon[pindex].vertex_index[2];

md2->polygon_array[pindex].vertex_index[1] = polygon[pindex].vertex_index[1];

md2->polygon_array[pindex].vertex_index[2] = polygon[pindex].vertex_index[0];

// 反轉紋理座標環繞順序

md2->polygon_array[pindex].textcoord_index[0] = polygon[pindex].textcoord_index[2];

md2->polygon_array[pindex].textcoord_index[1] = polygon[pindex].textcoord_index[1];

md2->polygon_array[pindex].textcoord_index[2] = polygon[pindex].textcoord_index[0];

}

else

{// 不改變頂點環繞順序

md2->polygon_array[pindex].vertex_index[0] = polygon[pindex].vertex_index[0];

md2->polygon_array[pindex].vertex_index[1] = polygon[pindex].vertex_index[1];

md2->polygon_array[pindex].vertex_index[2] = polygon[pindex].vertex_index[2];

// 不改變紋理座標環繞順序

md2->polygon_array[pindex].textcoord_index[0] = polygon[pindex].textcoord_index[0];

md2->polygon_array[pindex].textcoord_index[1] = polygon[pindex].textcoord_index[1];

md2->polygon_array[pindex].textcoord_index[2] = polygon[pindex].textcoord_index[2];

}

20.以繪畫順序讀取三角形頂點紋理座標,無需在提取幀是在生成.

int tindex0 = md2->polygon_array[pindex].textcoord_index[0];

int tindex1 = md2->polygon_array[pindex].textcoord_index[1];

int tindex2 = md2->polygon_array[pindex].textcoord_index[2];

21.讀取紋理座標轉換為0.0f~1.0f

md2->textcoord_array[pindex*3+0].u = (float)textcoord[tindex0].u / (float)header->skin_width;

md2->textcoord_array[pindex*3+0].v = (float)textcoord[tindex0].v / (float)header->skin_height;

md2->textcoord_array[pindex*3+1].u = (float)textcoord[tindex1].u / (float)header->skin_width;

md2->textcoord_array[pindex*3+1].v = (float)textcoord[tindex1].v / (float)header->skin_height;

md2->textcoord_array[pindex*3+2].u = (float)textcoord[tindex2].u / (float)header->skin_width;

md2->textcoord_array[pindex*3+2].v = (float)textcoord[tindex2].v / (float)header->skin_height;

}

22.遍歷每個頂點計算模型半徑

md2->radius_avg = 0;// 平均半徑

md2->radius_max = 0;// 最大半徑

for (int vindex = 0; vindex < header->vertex_num; ++vindex){

float dist;

dist = (float)sqrt(md2->vertex_array[vindex].x * md2->vertex_array[vindex].x +

md2->vertex_array[vindex].y * md2->vertex_array[vindex].y +

md2->vertex_array[vindex].z * md2->vertex_array[vindex].z);

md2->radius_avg = md2->radius_avg + dist;// 累加半徑

if (dist > md2->radius_max)

md2->radius_max = dist;// 最大半徑

}

23.計算平均半徑

md2->radius_avg = md2->radius_avg / header->vertex_num;//

23.載入默認動畫序列. md2_animations[]數組跟據上面動畫列表定義

for (int aindex = 0; aindex < (sizeof(md2_animations) / sizeof(MD2_ANIMATION)); ++aindex)

md2->animation_array[aindex] = md2_animations[aindex];

return true;

}

 

紋理需要另外載入,紋理高寬需為2x2x.如256256、128*128

bool Load_Texture_MD2(MD2_PTR md2, const char * path){

int index = md2->texture_num; // 索引

++md2->texture_num;// 紋理數量

Load_File_Texture(&md2->texture_array[index], path);// 載入

Bind_Image_Texture(&md2->texture_array[index]);// 綁定

return true;

}

 

3D模型動畫平滑運動需要幀插值.『當前幀』frame_curr帶有小數在0~127之間.按權重插值公式如下:

vi=v0(1-value) + v1value

逐幀提取函式:

void Extract_Frame_MD2(MD2_PTR md2,VECTOR3D_PTR  vertex_array,VECTOR3D_PTR normal_array,float frame_curr){

MD2_POLYGON_PTR polyon;

int vindex;

1.判斷幀是否插值得到

float ivalue = frame_curr – (int)frame_curr;

if (ivalue == 0.0f) {//判斷是否為整數

2.若為整數則直接讀取

int frame = (int)frame_curr;

if (frame >= md2->frame_num)

frame = md2->frame_num – 1;

3.計算當前幀索引頂點偏移

int base = md2->vertex_num * frame;

4.遍歷每個多邊形

for (int pindex = 0, index = 0; pindex < md2->polygon_num; ++pindex, index= index+3){

5.讀取多邊形每個頂點

polyon = &md2->polygon_array[pindex];

vindex = polyon->vertex_index[0];

vertex_array[index+0]    = md2->vertex_array[base + vindex];

vindex = polyon->vertex_index[1];

vertex_array[index + 1] = md2->vertex_array[base + vindex];

vindex = polyon->vertex_index[2];

vertex_array[index + 2] = md2->vertex_array[base + vindex];

6.計算三角形法線

Normal_VECTOR3D(&normal_array[pindex],&vertex_array[index + 0],&vertex_array[index + 1],&vertex_array[index + 2]);

}

}

else{

2.若有小數數則進行幀插值,讓動畫平滑

int frame0 = (int)frame_curr;

int frame1 = (int)frame_curr + 1;

if (frame0 >= md2->frame_num)

frame0 = md2->frame_num – 1;

if (frame1 >= md2->frame_num)

frame1 = md2->frame_num – 1;

int base0 = md2->vertex_num * frame0;

int base1 = md2->vertex_num * frame1;

3.偏曆三角形在兩幀之間插值計算頂點

for (int pindex = 0, index = 0; pindex < md2->polygon_num; ++pindex, index = index + 3){

4.讀取兩個多邊形頂點並行權重插值

polyon = &md2->polygon_array[pindex];

vindex = polyon->vertex_index[0];

vertex_array[index+0] = md2->vertex_array[base0 + vindex] * (1 – ivalue) +md2->vertex_array[base1 + vindex] * (ivalue);

vindex = polyon->vertex_index[1];

vertex_array[index + 1] = md2->vertex_array[base0 + vindex] * (1 – ivalue) +md2->vertex_array[base1 + vindex] * (ivalue);

vindex = polyon->vertex_index[2];

vertex_array[index + 2] = md2->vertex_array[base0 + vindex] * (1 – ivalue) + md2->vertex_array[base1 + vindex] * (ivalue);

5.計算三角形法線

Normal_VECTOR3D(&normal_array[pindex],&vertex_array[index + 0], &vertex_array[index + 1], &vertex_array[index + 2]);

}

}

}

 

載入MD2模型.讓MODEL3D

void Load_MD2_MODEL3D(MODEL3D_PTR model3D,MD2_PTR md2,int texture_index){

1.清零

memset(model3D, 0, sizeof(MODEL3D));

2.指向md2模型

model3D->md2 = md2;

3.分配每幀多邊形頂點記憶體

model3D->vertex_array = (VECTOR3D_PTR)malloc(md2->polygon_num * 3*sizeof(VECTOR3D));

4.分配每幀多邊形法線記憶體

model3D->normal_array = (VECTOR3D_PTR)malloc(md2->polygon_num * 3 * sizeof(VECTOR3D));

5.指向紋理

if (texture_index < md2->texture_num){

texture_index = md2->texture_num – 1;

model3D->texture = &md2->texture_array[texture_index];

}

}

 

設置模型動畫MD2有20個不同動作

void Set_Animation_MODEL3D(MODEL3D_PTR model3D,int anim,bool loop){

1.讀取MD2模型

MD2_PTR  md2= model3D->md2;

2.設定當前動畫索引

model3D->anim = anim;

3.動畫是否循環播放

model3D->loop = loop;

4.動畫播放標記設為未完成

model3D->complete = false;

5.動畫

MD2_ANIMATION_PTR  animation = md2->animation_array;

6.插幀頻率(0.0f~1.0f),1.0f表示不插幀

model3D->irate = animation[anim].irate;

7.當前幀

model3D->frame = animation[anim].start;

8.速度

model3D->speed = animation[anim].speed;

9.插幀計數器

model3D->count = 0;

10.提取動畫幀

Extract_Frame_MD2(model3D->md2,model3D->vertex_array,  model3D->normal_array,model3D->frame);

}

 

在遊戲引擎中你需要更新動畫, time為時間間隔通過Get_Counter_FPS(&fps);獲得

void Update_MODEL3D(MODEL3D_PTR model3D,float time){

MD2_PTR md2 = model3D->md2;

MD2_ANIMATION_PTR animation = md2->animation_array;

1.計算動畫有幾多幀

int   frame_num = animation->end – animation->start + 1;

2.計算每幀速度

float frame_speed = (float)frame_num / model3D->speed ;

3.當前幀帶小數以進行插值

model3D->frame = model3D->frame + frame_speed * time;

4.幀計數器控制插值

model3D->count = model3D->count + frame_speed * time;

5.判斷動畫是否播放完畢

if (model3D->frame > animation[model3D->anim].end)   {

if (model3D->loop == MD2_LOOP)  {// 循環播放動畫

model3D->count = 0;

model3D->frame = animation[model3D->anim].start;// 啟動幀

}

else{// 單次播放動畫

model3D->frame = animation[model3D->anim].end;// 結束幀

model3D->complete = true;// 以完成動作

}

}

  1. irate為插幀頻率(0.0f~1.0f), 1.0f表示不插幀

if (model3D->count >= model3D->irate || model3D->count == 0){

model3D->count = 0;//清零

8.提取動畫幀

Extract_Frame_MD2(model3D->md2, model3D->vertex_array,model3D->normal_array,model3D->frame);

}

}

 

每幀都要對3D模型進行渲染.這裡使用頂點數組進行渲染.當前你可以逐個三角形進行渲染但是會較慢.

void Render_MODEL3D(MODEL3D_PTR model3D){

1.當前矩陣堆棧壓棧

glPushMatrix();

  1. 移動多邊形位置並旋旋

glTranslatef(model3D->pos.x, model3D->pos.y, model3D->pos.z);// 移動

glRotatef(model3D->rot.x, 1.0f, 0.0f, 0.0f); // 繞X軸旋轉

glRotatef(model3D->rot.y, 0.0f, 1.0f, 0.0f); // 繞Y軸旋轉

glRotatef(model3D->rot.z, 0.0f, 0.0f, 1.0f); // 繞Z軸旋轉

3.壓入當前屬性

glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_TEXTURE_BIT);

4.提取MD2模型

MD2_PTR md2 = model3D->md2;

5.綁定紋理

TEXTURE_PTR texture = model3D->texture;

glEnable(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, texture->ID);

5.啟用頂點數組

glEnableClientState(GL_VERTEX_ARRAY);

6.啟用紋理座標數組

glEnableClientState(GL_TEXTURE_COORD_ARRAY);

7.啟用法線數組

glEnableClientState(GL_NORMAL_ARRAY);

8.指定頂點數組

glVertexPointer(3, GL_FLOAT, 0, model3D->vertex_array);

9.紋理座標

glTexCoordPointer(2, GL_FLOAT, 0, md2->textcoord_array);

10.三角形法線

glNormalPointer(GL_FLOAT,0, model3D->normal_array);

11.繪畫所有當前以啟用的頂點數組

glDrawArrays(GL_TRIANGLES, 0, md2->polygon_num * 3 );

12.啟用頂點數組

glDisableClientState(GL_VERTEX_ARRAY);

14.啟用紋理座標數組

glDisableClientState(GL_TEXTURE_COORD_ARRAY);

15.啟用法線數組

glDisableClientState(GL_NORMAL_ARRAY);

16.彈出當前屬性

glPopAttrib();

17.當前矩陣堆棧出棧

glPopMatrix();

}

MD2載入器程式:下載

  1. 可分別載入『角色』與『武器』MD2模型
  2. 紋理載入支持 『.PCX』『.BMP』『.TGA』
  3. 按ALT鍵彈出『MENU』點『ANIMATION』選切換不同的動畫.

評論