|
楼主 |
发表于 2005-3-11 20:15:51
|
显示全部楼层
其顶点位置为三个三维向量a,b,c。我们首先得到三角形的法向量 N:(下面的vector 表示三维向量,float表示浮点型标量)
vector v1 = b - a '由 a 指向 b 的边向量
vector v2 = c - a '由 a 指向 c 的边向量。
vector N = Normalize(v1 X v2) ' X代表叉乘,注意叉乘是有顺序的,如果v1和v2互换,结果向量将会反向。Normalize表示将所得结果单位化。所得的N就是三角形的法向量
vector S.N = N '三角形的法向量也就是平面 S 的法向量
然后,我们计算该平面与原点的距离 S.D:
float S.D = N * a ' *代表点乘,计算结果为三角形顶点a到法线S.N上的投影的长度,即距离S.D,为一标量。
有了法向量和距离,就可以表示一个平面了。如果想要对平面进行平移,只需修改 S.D 的值。
那么,怎样判断物体在平面的那一边呢?只需要比较物体位置在平面法向量上的投影长度和平面的 D 值就行了。假定我们的物体位置用3维向量 p 表示
float dp = S.N * p '点乘,dp为物体位置在平面 S 法线 N 上的投影长度,为一标量。
if (dp - S.D)>0 ,在平面 S 正面。
if (dp - S.D)<0 ,在平面 S 背面。
还有一个问题,就是怎样得到上文中提到的平面PS1,PS2和PS3。这个其实也不难,可以由下面的方法得到(以PS1为例):
vector PS1.N = Normalize(S.N X v1) '平面 S 法向 N 与 三角形边向量v1叉积并单位化,结果为PS1.N代表PS1的法向量。
float PS1.D = PS1.N * a - L'三角形顶点 a 在PS1的法向量上的投影长度,再减去 L
与之对应的平面PS4可以随之求得:
vector PS4.N = -PS1.N '两个平面的法向相对
float PS4.D = PS1.N * c - L '用顶点c点乘PS4的法向量,再减去 L
(#_# 这些公式真是令人头痛,我已经尽力想把它们说得清楚一些了。)
下面让我们进入实质性的阶段——代码的编写。
三、编写代码
还记得前不久在本站出现的一篇有关DirectX Mesh优化的文章吗?那里用到了一个非常简陋的所谓的“第一人称射击”游戏——“没有碰撞检测,没有光照,没有LightMap,没有BSP树,没有敌人(也就没有AI),没有武器:天那,什么也没有!”好吧,现在就让我们来给它加上碰撞检测。
下面是我后来向该程序中加入的三个自定义函数:Collision()、PreCheckCollision()和CheckCollision ()。其中,Collision()函数完成整个的碰撞检测过程。在其内部调用了另外两个函数。PreCheckCollision()主要判断物体位置(在该程序中就是玩家的位置)是否穿过了某个平面。CheckCollision()函数进一步判断穿过位置是否在三角形内部。
//=====================================================
// 函数名称:Collision()
// 函数功能: 完成碰撞检测
// 备注:使用优化后的场景网格模型
// 参数说明:
// Player_State * lpPslayer_State是代表玩家状态的
// 自定义结构。其中的vnewPos、
// vOldPos成员变量将被用于碰撞检测。
// 函数接受该结构的一个指针lpPs。
// LevelMap * lpMap evelMap是代表场景的自定义结构。
// 其中的m_pMesh成员变量是一个
// ID3DXMESH接口指针,保存地图的网
// 格信息。
//-----------------------------------------------------
HRESULT Collision(Player_State * lpPs,LevelMap * lpMap)
{
WORD * pIndexData;
CustomVertex * pVertexData;
D3DXVECTOR3 vNormal,vP1,vP2,vP3;
FLOAT fdistance;
if(lpMap == NULL)
return E_FAIL;
if(lpMap->m_pMesh == NULL)
return E_FAIL;
if(lpPs == NULL)
return E_FAIL;
if(lpMap->m_dwNumIndices == 0)
return E_FAIL;
if(lpMap->m_dwNumVertices == 0)
return E_FAIL;
在DirectX 8 Graphics中,场景的网格模型顶点缓冲和索引缓冲组成。顶点缓冲保存了所有的顶点的信息,而索引缓冲中的每三个元素代表一个三角形,这些元素的值是该三角形的三个顶点在顶点缓冲中的索引号。我们可以由此获得三角形的三个顶点的位置。下面的代码锁定网格模型中的顶点和索引缓冲,并得到指向它们的指针。
lpMap->m_pMesh->LockIndexBuffer(D3DLOCK_READONLY,(BYTE**)&pIndexData);
lpMap->m_pMesh->LockVertexBuffer(D3DLOCK_READONLY,(BYTE**)&pVertexData);
然后,遍历网格的每一个三角形:
for(WORD i = 0;i < lpMap->m_dwNumIndices; i+=3)
{
WORD a = pIndexData[i+0]; //a:三角形的第一个顶点的索引
WORD b = pIndexData[i+1]; //b:三角形的第二个顶点的索引
WORD c = pIndexData[i+2]; //c:三角形的第三个顶点的索引
D3DXVECTOR3 v1 = pVertexData.p - pVertexData[a].p;
//v1:边ab的向量
D3DXVECTOR3 v2 = pVertexData[c].p - pVertexData[a].p;
//v2:边ac的向量
D3DXVec3Cross(&vNormal,&v1,&v2); //叉积得到三角形法向量。
D3DXVec3Normalize(&vNormal,&vNormal);
fdistance = D3DXVec3Dot(&vNormal,&pVertexData[a].p)+5.0f;
//顶点a 在法向量上的投影长度,即平面与原点的距离。
if(PreCheckCollision(lpPs,&vNormal,fdistance) == 1)
{//如果玩家穿过了该三角形的平面
if(CheckCollision(lpPs,a,b,c,&vNormal,pVertexData) == 1)
{//如果是在三角形三边范围之内穿过的
//修改玩家的位置,使之不能穿墙。
lpPs->vnewPos += vNormal*(fdistance
- D3DXVec3Dot(&vNormal,&lpPs->vnewPos)+0.2);
}
}
//否则,继续循环,判断下一三角形。
}
//循环结束,在退出前必须解锁顶点和索引缓冲。
lpMap->m_pMesh->UnlockVertexBuffer();
lpMap->m_pMesh->UnlockIndexBuffer();
return S_OK;
}
下面是函数PreCheckCollision()和CheckCollision()的代码。
//=================================================
// 函数名reCheckCollision()
// 功能: 判断玩家是否穿过了当前平面。
// 参数说明:
// _Player_State* lpps: 玩家的状态,包含位置信息。
// vNromal: 当前平面的法向量(已单位化)
// fdistance: 当前平面与原点的距离
// 返回值: 如果需要进一步检测,返回1
// 否则返回0。
//-------------------------------------------------
INT PreCheckCollision(Player_State* lpps,D3DXVECTOR3* pvNormal,FLOAT fDist)
{
float StartSide,EndSide;
//计算玩家的旧位置与平面的距离
StartSide = D3DXVec3Dot(pvNormal,&lpps->vOldPos) - fDist;
//计算玩家的新位置与平面的距离
EndSide = D3DXVec3Dot(pvNormal,&lpps->vnewPos) - fDist;
//如果玩家的旧位置在平面之前而新位置在平面之后,
//说明穿过了平面。
if(StartSide>0 && EndSide <=0) return 1;
else return 0;
}
//==============================================
// 函数名称:CheckCollision
// 功能: 判断物体是否在三角形内穿过。
// 参数说明:
// _Player_State* lpps :玩家的状态,包含位置信息。
// a,b,c :三角形的顶点在顶点
// 缓冲中的索引。
// D3DXVECTOR3* vNormal:三角形的法向量
// CustomVertex* pVertices:指向顶点缓冲的指针
// 返回值: 如果玩家在三角形内部穿过,返回1
// 否则返回0
//----------------------------------------------
INT CheckCollision(Player_State* lpps,WORD a,WORD b,WORD c,D3DXVECTOR3 * pvNormal,CustomVertex * pVertices)
{
D3DXVECTOR3 PerPlaneNormal;
//垂直平面(perpendicular plane)的法向量
float fPerPlaneDist;
//垂直平面与原点的距离。
D3DXVECTOR3 v1;
//用于记录三角形当前边的向量。
//============================================================
// 检查物体是否在过三角形第一条边AB的垂直平面
// 和与它相对的平面之内。
// 首先计算三角形的AB边向量。
v1 = pVertices.p-pVertices[a].p;
// 计算过该边的垂直平面的法向量。
D3DXVec3Cross(&erPlaneNormal,pvNormal,&v1);
D3DXVec3Normalize(&erPlaneNormal,&erPlaneNormal);
// 保证垂直边的法向量指向三角形内部。
if(D3DXVec3Dot(&(pVertices[c].p-pVertices[a].p),&erPlaneNormal)<0)
PerPlaneNormal = -PerPlaneNormal;
// 计算该垂直平面与原点的距离,并减去物体与三角形能够接近的最短距离。
fPerPlaneDist = D3DXVec3Dot(&erPlaneNormal,&pVertices[a].p) - 5.0f;
// 如果物体在此垂直平面之外,返回0。
if(D3DXVec3Dot(&erPlaneNormal,&lpps->vOldPos)-fPerPlaneDist<0)
return 0;
// 由于此垂直平面与其相对垂直平面平行,
// 所以直接计算相对平面与原点的距离。
fPerPlaneDist = D3DXVec3Dot(&erPlaneNormal,&pVertices[c].p) + 5.0f;
// 如果物体也在此垂直平面的相对平面外,返回0。
if(D3DXVec3Dot(&erPlaneNormal,&lpps->vOldPos)-fPerPlaneDist>0)
return 0;
//=============================================================
// 下面检查物体是否在过边BC的垂直平面以及它的相对平面之内。
// 步骤与上面基本相似。
v1 = pVertices[c].p-pVertices.p;
D3DXVec3Cross(&erPlaneNormal,pvNormal,&v1);
D3DXVec3Normalize(&erPlaneNormal,&PerPlaneNormal);
if(D3DXVec3Dot(&(pVertices[a].p-pVertices.p),&PerPlaneNormal)<0)
PerPlaneNormal = -PerPlaneNormal;
fPerPlaneDist = D3DXVec3Dot(&PerPlaneNormal,&pVertices.p) - 5.0f;
if(D3DXVec3Dot(&PerPlaneNormal,&lpps->vOldPos)-fPerPlaneDist<0)
return 0;
fPerPlaneDist = D3DXVec3Dot(&PerPlaneNormal,&pVertices[a].p) + 5.0f;
if(D3DXVec3Dot(&PerPlaneNormal,&lpps->vOldPos)-fPerPlaneDist>0)
return 0;
//=============================================================
// 然后是过边CA的垂直平面和它的相对平面。
v1 = pVertices[a].p-pVertices[c].p;
D3DXVec3Cross(&PerPlaneNormal,pvNormal,&v1);
D3DXVec3Normalize(&PerPlaneNormal,&PerPlaneNormal);
if(D3DXVec3Dot(&(pVertices.p-pVertices[c].p),&PerPlaneNormal)<0)
PerPlaneNormal = -PerPlaneNormal;
fPerPlaneDist = D3DXVec3Dot(&PerPlaneNormal,&pVertices[c].p) - 5.0f;
if(D3DXVec3Dot(&PerPlaneNormal,&lpps->vOldPos)-fPerPlaneDist<0)
return 0;
fPerPlaneDist = D3DXVec3Dot(&PerPlaneNormal,&pVertices.p) + 5.0f;
if(D3DXVec3Dot(&PerPlaneNormal,&lpps->vOldPos)-fPerPlaneDist>0)
return 0;
//如果物体在上面任意六个平面之外,则函数早已返回0。
// 如果到这里还没有返回,说明物体在六面之内,
// 应返回1。
return 1;
}
注:上面的三个函数是我按照该算法的原理自己写的,尽管在我的程序中运行正常,但是由于水平有限,难免会有疏漏之处,而且由于是针对例程而写,所以并没有太好的通用性。但是在函数中这种碰撞检测的算法还是比较忠实地体现出来了的。总之是为了更进一步的说明该算法。 |
|