遊戲建模之碰撞時間

遊戲建模之碰撞時間

『物體』運動核心是碰撞.你試想下當『物體』高速運動時有可能會穿越牆壁.這因遊戲世界中CPU會輪詢處理所有『物體』. 並只會分到有限『運算/時間』,所以每一次你都需要準確計算碰撞時間.通過不斷遞歸而確定最後移動位置

通過使用基於時間二維方程式進行碰撞檢測.

xf=x0+v0t+(1/2)at2

ax2+bx+c=0

利用係數替換可得到

a=(1/2)*a

b=v0

c=x0-xf

將其擴展可得到三維方程式

xt=cx+bxt+axt2

yt=cy+byt+ayt2

zt=cz+bzt+azt2

如果將三維方程應用於平面方程可得

Axt+Byt+C*zt+D=0

此方程說面如果物體與平面發生碰撞.其碰撞點位於(xt,yt,zt)通過代數運算可得到

(Aax+Bay+Caz)x2+(Abx+Bby+Cbz)x+(Acx+Bcy+Ccz)+D=0

這其實就是平面二次方程式(ax2+bx+c=0) ,其點積形式為:

ax2=(Aax+Bay+Caz)x2=N•(1/2)A

利用點積得到二次方程係數:

加速度係數: a=N•(0.5A)

速度係數:  b=N•V

距離係數:  c=N•X+D

有上序二次方程係數就可計算碰撞時間

float a = n % (acceleration * 0.5f);// 加速度係數

float b = n % velocity;// 速度係數

float c = n % position + D – radius;// 距離係數

若a=0加速度係數為零,碰撞時間等於距離係數除以速度係數.因為距離係數小於零所以要去負值.

collisionTime = -c/b;

若a!=≠0加速度係數不為零,可以使用二次方程計算碰撞時間:

x=(-b+√(b2-4ac) ) / 2a

在計算碰撞時間之前可先計算D=b2-4ac 若D小於零則無解.若D大於零則有解

float D = b * b – 4 * a*c;

if (D > 0) // 如果判斷式大於等於零

collisionTime = (-b – sqrtf(D)) / (2 * a); // 計算碰撞時間

因為發生碰撞物體會彈開,方向矢量產生返射需要重新計算位置.通過減去碰撞時間進行遞歸.直到『幀時間』為零

 

下面是『曲棍球』碰撞函式.

puck 是曲棍球.

table 是球臺

deltaTime是幀時間

void Update_Puck(PUCK_PTR puck, TABLE_PTR table, float deltaTime){

float  fastestTime = deltaTime;// 最早碰撞時間

float  collisionTime;// 碰撞時間

PLANE3D_PTR plane          = NULL;// 平面

PLANE3D_PTR planeCollision = NULL;// 碰撞平面

if (deltaTime <= 0.000f)// 遞歸

return;

// 對球臺四個圍邊檢查碰撞

for (int i = 0; i < 4; i++){

plane = &table->wall[i];// 平面

// 點積二次方程

float a = plane->n % (puck->acceleration * 0.5f);// 加速度係數

float b = plane->n % puck->velocity;// 速度係數

float c = plane->n % puck->position + plane->D – puck->radius;// 距離係數

if (a == 0 && b != 0 && c != 0){// 如果無加速度

// 碰撞時間等於距離除以速度

collisionTime = -c/b;// 碰撞時間

if (collisionTime >= 0 && collisionTime < fastestTime){// 發生碰撞

fastestTime = collisionTime;// 保存碰撞時間

planeCollision = plane;// 平面

}

}

else

if(a != 0){

// 加速度a不等於零

// 計算判別式

float D = b * b – 4 * a*c;

if (D > 0) {// 如果判斷式大於等於零

// 計算碰撞時間

collisionTime = (-b – sqrtf(D)) / (2 * a);

if (collisionTime >= 0.0f && collisionTime < fastestTime) {// 發生碰撞

fastestTime = collisionTime;

planeCollision = plane;

}

}

}

}// END FOR

// 速度設上限每秒800米

if (Length_VECTOR3D(&puck->velocity) > 800)

puck->velocity = Normalize_VECTOR3D(&puck->velocity) * 800;

// 如果冰球正移動,則應用磨擦力

if (Length_VECTOR3D(&puck->velocity) > 0)

puck->acceleration = -puck->velocity * 0.2f;// 計算加速度

// 計算當前位置

puck->position = puck->position + puck->velocity * fastestTime + puck->acceleration * (fastestTimefastestTime0.5f);

// 應用磨擦力

puck->velocity = puck->velocity + puck->acceleration * fastestTime;

// 如果發生碰撞,反轉速度

if (planeCollision != NULL)// 碰撞平面

puck->velocity = Reflection_VECTOR3D(&puck->velocity, &planeCollision->n);

// 遞歸調用

Update_Puck(puck, table, deltaTime – fastestTime);

}

遊戲建模之碰撞反應

遊戲建模之碰撞反應

若3D模型發生碰撞後需要計算碰撞反應,不通物體運動有不同碰撞反應.但物體多數以直線運動.物體彈回角度和碰撞角度相等.

入射角度:桌球運動方向與邊沿平面法線向量之間夾角.

反射角度:垂直於運動方向矢量

例:當桌球撞向邊沿.它將按撞擊角度與之對等『入射角度』彈開

方程並沒有考慮球體旋轉作用力與磨擦力.最終得到計算反射方向方程式

給定運動方向矢量I與垂直法線N求碰撞反射方向F

F = (I – N2 (I % N)) * | I |;

計算反射方向代碼,dir為射線方向,normal為碰撞面法線

VECTOR3D Reflection_VECTOR3D(VECTOR3D_PTR dir,VECTOR3D_PTR normal){

VECTOR3D vec ;

Normalize_VECTOR3D(dir, &vec); // 單為化方向向量

*dir = (vec – *normal * 2.0f * (vec % *normal)) * Length_VECTOR3D(dir);

return *dir;

}

遊戲建模-平面碰撞

遊戲建模-平面碰撞

平面是3D圖形學重要部分.平面有兩個重要概念.

1.3D平面都無窮遠延伸

2.所有平面都將整個空間分成兩個半空間.正半空間為法線指向空間,負半空間則是令一則空間.這個特性對於碰撞算法重要

平面描敘如下:

平面法線向量:n=(a,b,c)

平面任意點:p0=(x0,y0,z0)

平面任意點:p=(x,y,z)

平面與原點距離:d平面位移常量(plane-shift constant)

因為法線n與向量(p0->p)垂直. 因兩垂直向量點積為零:

n * (p0->p) = 0

轉為分量表示

(a,b,c)*(x-x0,y-y0,z-z0)=0

轉為『頂點』與『法線』表示

a(x-x0)+b(y-y0)+c*(z-z0)=0

ax+by+cz+(-ax0-by0-cz0)=0

令d=-ax0-by0-c*z0

『平面方程』如下:

ax+by+c*z+d=0

 

這足以定義平面結構

typedef struct PLANE3D_TYP{

VECTOR3D n;//平面法線向量(不必是單位向量)

float dist;//平面到原點最近距離

POINT3D p0;//平面上最近原點的點

}PLANE3D,*PLANE3D_PTR;

 

判斷『點』位於『平面』那個半空

3D世界中『視點』、『角色』並不能穿越牆壁.要做到這步需判斷點位於平正空間(法線指向)還是負空間.

要判斷這點需要下面的平面方程

hs=a(x-x0)+b(y-y0)+c*(z-z0)

只需將點(x,y,z)帶入方程中並計算結果

如果hs=0則該點位於平面之上

如果hs>0則該點位於平面正半空間(法線指向)中

如果hs<0則該點位於平面負半空間中

float Compute_Point_In_Plane3D(PLANE3D_PTR plane,VECTOR3D_PTR pt){

float hs = plane->A * pt->x + plane->B * pt->y + plane->C * pt->z + plane->D;

return(hs); // 返回半空間值

}

 

計算平面與直線之相交點(3D空間)

平面『頂點』與『法線』方程如下:

a(x-x0)+b(y-y0)+c*(z-z0)=0

3D直線方程如下:

(x-x0)/a = (y-y0)/b = (z-z0)/c

計算多邊形法線與直線方向點積

double a = plane->n % (line->v);

若為零則直線與平面平行

VECTOR3D Intersect;

if (a == 0)

Intersect = line->p0;

計算相交點

Intersect = line->p0 – (line->v) * ((plane->n % line->p0 + plane->D) / a);

3D線段與3D平面交點函式:

bool Intersect_Line3D_Plane3D(PLANE3D_PTR plane, PARMLINE3D_PTR line,POINT3D_PTR pt){

// 計算直線方向與多邊形法線點積

double a = plane->n % (line->v);

if (a == 0)

return false;

// 計算相交點

*pt = line->p0 – (line->v) * ((plane->n % line->p0 + plane->D) / a);

return true;

}

 

計算頂點是否為於多邊形之上,

首先計算頂點與多邊形所有頂點之間角度總和.如果該頂點位於多邊形之上.那麼此頂點與多邊形所有頂點之間角度總和將等於或接近2PI

bool Intersect_Vector3D_Polygon3D(VECTOR3D_PTR polygon, int num,VECTOR3D_PTR v){

VECTOR3D segment1, segment2;// 頂點 到 多邊形頂點 矢量

double length1, length2;// 矢量長度

double sumAngle = 0;// 矢量之間角度總和

double cosAngle = 0;// 兩矢量余弦角

// 編曆多邊形所有頂點

for (int index = 0; index < num; ++index)     {

segment1 = polygon[index] – *v;

segment2 = polygon[(index+1) % num] – *v; // % num 確保數值環形加一

length1 = Length_VECTOR3D(&segment1);

length2 = Length_VECTOR3D(&segment2);// 矢量長度

//檢查頂點是否落在多邊形邊界上

if ( length1 * length2 <= 0.0000001f){

// 多邊形邊界被認為是多邊形內部

sumAngle = PI2; // pai * 2

break;

}

// 計算上述兩個矢量之間余弦角

cosAngle = (segment1 % segment2) / (length1 * length2);

// 將計算結果累加入角度總和

sumAngle = sumAngle + acosf(cosAngle);

}

if ((sumAngle <= PI2 + 0.0000001f) && (sumAngle >= PI2 – 0.0000001f))

return true;// 頂點與多邊形發生碰撞

else

return false;

return true;

}

 

使用三個頂點初始化(定義)3D平面!

void Init_PLANE3D(PLANE3D_PTR plane,VECTOR3D_PTR va,VECTOR3D_PTR vb,VECTOR3D_PTR vc){

VECTOR3D normalA = *vc – *va;// 計算頂點C->A向量

VECTOR3D normalB = *vc – *vb;// 計算頂點C->B向量

plane->n = Cross_VECTOR3D(&normalA, &normalB);// 計算兩個3D向量叉積

plane->D = Dot_VECTOR3D(&(-(*va)), &plane->n); //距離等於va求返後與n求點積

}

遊戲建模-矢量運算

遊戲建模-矢量運算

矢量(VECTOR)也稱『向量』其實是抽象『量』它在遊戲世界被頂義為『位置』『速度』『磨擦』『方向』『點』等等. 矢量通常有3種

矢量 分量 簡序
2D矢量 x,y 2D空間
3D矢量 x,y,z 3D空間
4D矢量 x,y,z,w 3D空間w總是為1.用於方陣運算

首先定義3D矢量結構:

typedef struct VECTOR3D_TYP{

float x,y,z;

}VECTOR3D,*VECTOR3D_PTR;

下面是3D矢量(VECTOR)運算函式庫.

 

計算3D矢量長度,矢量長度也稱為範數(norm).將其理解為原點(0,0,0)到矢量(x,y,z)之距離

float Length_VECTOR3D(VECTOR3D_PTR va){

return( (float)sqrtf(va->xva->x + va->yva->y + va->z*va->z) );

}

 

3D矢量進行歸一化(normalize),也就使其長度縮放為1,但同時方向保持不變.它通常被用於無需理會長度之運算如『方向』

void Normalize_VECTOR3D(VECTOR3D_PTR va){

// 1.首先計算長度

float length = sqrtf(va->xva->x + va->yva->y + va->z*va->z);

//2.矢量除以長度得到歸一化矢量

va->x= va->x/length;

va->y= va->y/length;

va->z= va->z/length;

}

 

3D矢量點積運算可以理解為矢量乘法.先將各分量相乘後再相加得到一個標量

float operator%(VECTOR3D va, VECTOR3D vb){

return((va.x * vb.x) + (va.y * vb.y) + (va.z * vb.z));

}

float Dot_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){

return( (va->x * vb->x) + (va->y * vb->y) + (va->z * vb->z) );

}

叉積是另一種矢量乘法,叉積運算最小要有3個分量才有效.

VECTOR3D operator^(VECTOR3D va, VECTOR3D vb){

VECTOR3D vn;

vn.x = ((va.y * vb.z) – (va.z * vb.y));

vn.y = -((va.x * vb.z) – (va.z * vb.x));

vn.z = ((va.x * vb.y) – (va.y * vb.x));

return(vn);

}

VECTOR3D Cross_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){

VECTOR3D vn;

vn.x =  ( (va->y * vb->z) – (va->z * vb->y) );

vn.y = -( (va->x * vb->z) – (va->z * vb->x) );

vn.z =  ( (va->x * vb->y) – (va->y * vb->x) );

return(vn);

}

 

計算兩個3D矢量va和vb之間夾角余弦值

float CosTh_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){

return(Dot_VECTOR3D(va,vb)/(Length_VECTOR3D(va)*Length_VECTOR3D(vb)));

}

 

 

計算三角形法線

void Normal_VECTOR3D(VECTOR3D_PTR normal,VECTOR3D_PTR va, VECTOR3D_PTR vb, VECTOR3D_PTR vc){

VECTOR3D u, v, n;

float length;

u = *vb – *va;

v = *vc – *va;

n = u^v;//Cross_VECTOR3D(&u, &v, &n);// 計算叉積

length = sqrtf(n.xn.x + n.yn.y + n.z*n.z);

normal->x = n.x/length;

normal->y = n.y/length;

normal->z = n.z/length;

}

 

將兩個3D矢量相加(va + vb),如用於位置移動

VECTOR3D operator+(VECTOR3D va, VECTOR3D vb){

VECTOR3D vsum;

vsum.x = va.x + vb.x;

vsum.y = va.y + vb.y;

vsum.z = va.z + vb.z;

return (vsum);//返回相加結果!

}

 

將兩個3D矢量相減(va – vb),如用於位置移動

VECTOR3D operator-(VECTOR3D va, VECTOR3D vb){

VECTOR3D vdiff;

vdiff.x = va.x – vb.x;

vdiff.y = va.y – vb.y;

vdiff.z = va.z – vb.z;

return(vdiff);     //返回相減向量!

}

 

3D矢量反數,如返轉方向

VECTOR3D operator-(VECTOR3D v){

VECTOR3D negation;

negation.x = -v.x ;

negation.y = -v.y ;

negation.z = -v.z ;

return(negation);     //返回反數向量!

}

 

使用縮放因子k對3D矢量進行縮放如:位置=位置+速度*時間

VECTOR3D operator*(VECTOR3D va, float k){

VECTOR3D vscaled;

vscaled.x = k * va.x;

vscaled.y = k * va.y;

vscaled.z = k * va.z;

return vscaled;// 返回縮放後向量

}

 

3D矢量賦值

void Init_VECTOR3D(VECTOR3D_PTR v, float x,float y,float z) {

v->x = x;  v->y = y; v->z = z;

}

 

3D矢量拷貝

Copy_VECTOR3D(VECTOR3D_PTR vdst, VECTOR3D_PTR vsrc){

vdst->x = vsrc->x;  vdst->y = vsrc->y; vdst->z = vsrc->z;

}

 

3D矢量比較

bool operator==(VECTOR3D vdst, VECTOR3D vsrc){

if (vdst.x == vsrc.x && vdst.y == vsrc.y && vdst.z == vsrc.z)

return true;

else

return false;

}

 

3D向量不等比較

bool operator!=(VECTOR3D vdst, VECTOR3D vsrc)

{

if (vdst.x != vsrc.x ||         vdst.y != vsrc.y ||vdst.z != vsrc.z)

return true;

else

return false;

}

 

向量歸零(3D向量)無方向,無距離,代表位於原點

void Zero_VECTOR3D(VECTOR3D_PTR v) {

v->x = v->y = v->z = 0.0f;

}

計算兩矢量之間夾角

float Angle_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){

return acosf(*va % *vb);

}

角度轉弧度

#define DEG_TO_RAD(ang) ((ang)*PI/180.0)

弧度轉角度

#define RAD_TO_DEG(rads) ((rads)*180.0/PI)

隨機數 x:下限,  y:上限.

#define RAND_RANGE(x,y) ( (x) + (rand()%((y)-(x)+1)))

隨機數: -1.0 ~ 1.0

#define FRAND_RANGE1()   (((float)rand()-(float)rand())/RAND_MAX)

隨機數 0~1

#define FRAND_RANGE() ((float)rand() / (float)RAND_MAX)

遊戲建模之邊界盒

遊戲建模之邊界盒

邊界球』雖然可解卻大部3D模型『碰撞測試』問題.單若3D模型是長條形則不適合如『牆體』『長劍』.『軸對齊坐標邊界盒』axis-aligned bounding box(AABB)引入則可解決這類問題.每個『邊界盒』均由3D模型『中心點』與『最遠點』、『最近點』所組成.通過遍歷每個3D模型頂點找出XYZ三軸上最遠頂點.定義3D邊界盒:

typedef struct AABB_TYP {

VECTOR3D center;// 中心點

VECTOR3D far;// 最遠

VECTOR3D near;// 最近

}AABB, *AABB_PTR;

 

『3D模型』最遠點與最近點可遍歷所有3D模型頂點獲得:

1.最遠點與最近點清零

VECTOR3D far = {0,0,0};// 最遠點

VECTOR3D near = {0,0,0};// 最近點

2.遍歷所有3D頂點

for (int index = 0; index < vertex_num; ++index){

3.XYZ三軸最遠點

if (far.x > vertex_array[index].x)

vertex_array[index].x = far.x;

if (far.y > vertex_array[index].y)

vertex_array[index].y = far.y;

if (far.z > vertex_array[index].z)

vertex_array[index].z = far.z;

4.XYZ三軸最近點

if (near.x > vertex_array[index].x)

vertex_array[index].x = near.x;

if (near.y > vertex_array[index].y)

vertex_array[index].y = near.y;

if (near.z > vertex_array[index].z)

vertex_array[index].z = near.z;

}

 

判斷頂點是否為於邊界盒

bool Compute_Point_In_AABB(AABB_PTR aabb, VECTOR3D_PTR point)

{

if ((point->x >= aabb->center.x + aabb->near.x && point->x <= aabb->center.x + aabb->far.x) &&

(point->y >= aabb->center.y + aabb->near.y && point->y <= aabb->center.y + aabb->far.y) &&

(point->z >= aabb->center.z + aabb->near.z && point->z <= aabb->center.z + aabb->far.z) )

return true;

return false;

}

遊戲建模之邊界球

遊戲建模之邊界球

在3D遊戲中常對『3D模型』進行多邊形『碰撞檢測』.例如武器擊中『牆體』或『怪物』.最容易最常用是『邊界球』進『碰撞檢測』.每個『邊界球』均由3D模型『中心點』與『半徑』. 這『半徑』並不一定是最長半徑,通常這個值只包裹核心部分.定義3D球體:

typedef struct SPHERE3D_TYP{

float    x, y, z;// 中心點

float    radius;// 球體半徑

}SPHERE3D,* SPHERE3D_PTR;

 

『3D模型』最大半徑與平均平徑可遍歷所有頂點而取得:

1.模型半徑前設為零

radius_avg = 0;// 平均半徑

radius_max = 0;// 最大半徑

2.遍歷3D模型所有頂點

for (int index = 0; index < vertex_num; ++index)

{

3.計算3D頂點與中心距離

float dist = (float)sqrt(vertex_array[index].x*vertex_array[index].x +

vertex_array[index].y*vertex_array[index].y +

vertex_array[index].z *vertex_array[index].z);

4.累加半徑

radius_avg = radius_avg + dist;

5.求得最大半徑

if (dist > radius_max)

radius_max = dist;

}

6.計算平均半徑

radius_avg = radius_avg / vertex_num;

 

要對兩『邊界球』進行『碰撞檢測』只需求得兩『邊界球』之距,然後與兩『邊界球』半徑之和進行比較:

bool Compute_Sphere3D_In_Sphere3D(SPHERE3D_PTR sphereA, SPHERE3D_PTR sphereB)

{1.計算兩頂點XYZ分量距離

float x = sphereA->x – sphereB->x;

float y = sphereA->y – sphereB->y;

float z = sphereA->z – sphereB->z;

2.球體距離

float dist = sqrtf(x*x + y * y + z * z);

3.半徑之和進行比較

if (dist < (sphereA->radius + sphereB->radius))

return true;// 球體重疊/碰撞

else

return false;//

}

Visual Studio之編譯宏定義

Visual Studio之編譯宏定義

在未有win64出現之前只需要編譯x86程式.但出現win64後需要分別編譯x86與x64兩個版本.你需要分開進行編譯.

Visual Studio分別為平臺生成全域宏:

x86下編譯定義_WIN32宏

x64下編譯定義_WIN64宏

#ifdef _WIN64

// x64平臺

#else

// x86平臺

#endif

 

Visual Studio調試模式生成全域宏:

調試模式下編譯定義_DEBUG宏

發行模式下編譯無_DEBUG宏定義

#ifdef  _DEBUG

//調試模式

#else

//發行模式

#endif

 

DirectX SDK下載與設定

DirectX SDK下載與設定

DirectX本是用於取替OpenGL給遊戲廠商使用.但遊戲廠商集體反抗.才另microsoft支持OpenGL.而且自DirectX6全面使用COM模型開發.DirectX是設計用於『影片』『聲音』『輸入』『網絡』抽象軟件界面結口.DirectX接口由microsoft定義.而底層則有驅動程式與硬件進行通信.開發者完全無需理會硬件之間差異.如果硬件不支持則由DirectX進行模擬.

  1. DirectX SDK 下載『DirectX Software Development Kit
  2. 下載『exe』運行後自動進行解壓.
  3. 其實你只需要『Lib』與『Include』這兩個『資料夾』.『Lib』下面分別有『x86』與『x64』
  4. 接下來讓工程生成『DirectX』目錄並複製『Lib』與『Include』下所有『頭文檔』與『庫文檔』
  5. 包含『頭文檔』

#include “..\DirectX\Include\ddraw.h”

#include “..\DirectX\Include\dinput.h”

#include “..\DirectX\Include\dsound.h”

  1. 包含『庫文檔』你編譯時需要分開x86與x64的LIB文檔

#ifdef  _WIN64

#pragma comment(lib, “DirectX\Lib\x64\ddraw.LIB”)

#pragma comment(lib, “DirectX\Lib\x64\dinput8.LIB”)

#pragma comment(lib, “DirectX\Lib\x64\dxguid.LIB”)

#pragma comment(lib, “DirectX\Lib\x64\dsound.LIB”)

#else

#pragma comment(lib, “DirectX\Lib\x86\ddraw.LIB”)

#pragma comment(lib, “DirectX\Lib\x86\dinput8.LIB”)

#pragma comment(lib, “DirectX\Lib\x86\dxguid.LIB”)

#pragma comment(lib, “DirectX\Lib\x86\dsound.LIB”)

#endif

 

上面方法是指定工程目錄.令外你還可制定Visual Studio搜索目錄:

  1. 設定『項目』『屬性』『VC++目錄』.然後分別設定『Include目錄』與『程式庫目錄』
  2. 『Include目錄』包含『D:\ DirectX\Include』.
  3. Win32平臺『程式庫目錄』包含『D:\DirectX\Lib\x64』
  4. Win64平臺『程式庫目錄』包含『D:\DirectX\Lib\x86』
  5. 在包含『頭文檔』與『庫文檔』時無需設定相對路徑

 

在DirectX8之前分別使用DirectSound和DirectMusic處理音頻播放. DirectSound用于處理聲波回放和捕足,而DirectMusic則是加載和播放所有聲音主要組件.但在DirectX8之後合平DirectXAudio組件.若你需要播放MIDI睇『DirectMusic之播放MIDI

 

 

 

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』選切換不同的動畫.

WAV音檔分釋與讀取

WAV音檔分釋與讀取

WAV音頻格式由Electronic Arts(電子協會)創建.它基於.IFF(Interchange File Format)對等交換文件格式.允許不同音檔格式通過鑲套技術進行編碼.

上兩編文章『DirectSound』與『DirectSound3D』均從WAV音檔中讀音頻數據.WAV音檔好再於結構簡單無需解壓. 我建議先把所有WAV音檔數據格式統一. 改為『單聲道Mono』(因為你只有一個咪頭)、採樣頻率11025Hz、採樣精度8bit.後再由遊戲引擎載入.

.WAV數據由以下三部份組成:

數據 簡序
RIFF .IFF標記
FORMAT 音頻格式
DATA 音頻數據

 

RIFF數據 所占空間(BYTE) 簡序
chunkID 4 塊ID,必須為’RIFF’
chunkSize 4 塊長度(不包含chunkID[4]和chunkSize所占空間)

 

FORMAT數據 所占空間(BYTE) 簡序
waveID 4 WAVE ID,必須為’WAVE’
chunkID 4 塊ID,必須為’fmt ‘
chunkSize 4 塊長度(不包含chunkID[4]和chunkSize所占空間)
wFormatTag 2 壓縮(格式)標誌默認WAVE_FORMAT_PCM脈衝編碼格式
wChannels 2 聲道:『單聲道Mono』或『雙聲道Stereo』
dwSamplesPerSec 4 採樣頻率(11025Hz,22050Hz,44100Hz)
dwAvgBytesPerSec 4 每秒播放字節數(SamplesPerSec * BlockAlign)
wBlockAlign 2 字節對齊,單聲道8bit占1byte.雙聲道16bit占4yte.
wBitsPerSample 2 每個採樣點位精度.有8bit、16bit、24bit、32bit
information 2 附加信息(未必有此數據)

 

數據塊 所占空間(BYTE) 簡序
chunkID 4 塊ID,必須為’data’
chunkSize 4 音頻數據長度
Data chunkSize 音頻數據

讀取WAV數據.wav指向文檔數據.size為文檔長度

bool Load_WAV(SOUND3D_PTR sound3D, PBYTE wav, int size){

1.數格塊

RIFF_PTR          riff;

WAVE_FORMAT_PTR   format;

WAVE_DATA_PTR     data;

2.讀取RIFF數據

riff = (RIFF_PTR)wav;

3.判斷是否RIFF

if (riff->id != WAVE_RIFF_ID)

return false;        // 返回出錯!

4.讀取WAVE_FORMAT_CHUNK

format = (WAVE_FORMAT_PTR)((PBYTE)wav + sizeof(RIFF));

if (format->waveID != WAVE_ID ||

format->chunkID != WAVE_FMT_ID ||

format->wFormatTag != WAVE_FORMAT_PCM)

return(false);   // 返回出錯!

5.讀取數據塊

data = (WAVE_DATA_PTR)((PBYTE)wav + sizeof(RIFF) + (int)format->chunkSize + sizeof(WAVE_CHUNK));

if (data->chunkID != WAVE_DATA_ID)

return(false);   // 返回出錯!

6.獲取音頻數據

PBYTE audio = (PBYTE)malloc(data->chunkSize);

memcpy(audio, &data->data, data->chunkSize);

return true;

}