在实际应用中,常常使用包围球或者包围盒代替物体与拾取射线进行相交测试,假设拾取射线与包围盒或者包围球相交,就认为拾取射线与物体相交了,其实,在3d世界中有很多的计算都是不精确的,只是简单的模拟,近似的操作。因为确实也不需要必须要完全精确。
以包围球为例子,包围球可以用球心o与半径r来表示,假设点p在包围球上,即满足条件:
||p-o|| = r
即点p到球心o的距离等于球体半径r,
射线方程为:
p(t)=po+tu
po表示射线的起点,u表示射线的方向,t表示一个标量。因此,可以用这两个向量来定义射线的结构体,
定义如下结构:
struct ray
{
D3DXVECTOR3 _origin;//射线起点
D3DXVECTOR3 _derction;//射线方向
};
假设射线与球体相交,必然存在一个点pt既在射线上又在球面上,将射线方程带入球面方程即可得到:
||pt-o|| = r //pt在圆面上 那么pt到圆心o的距离等于r
||po+tu||-r=o//如果p(t)=po+tu在圆面上,那么满足||po+tu||=r 用这个方程求到的射线上的点pt满足在圆上
显然,要满足这个条件,就是求解方程中t的值,把公式进行转换:
(po+tu-o)2=r2
将最后的公司转化为一元二次方程:
A=u2
B=2u(p0-0)
C=(po-o)2-r2
At2+Bt+c=0
由于射线的方向u是单位向量,因此A=1,对上面的方程求解可以得到以下两个答案:
t0 =(-B+(B2-4c)的开平方)/2
t1 =(-B-(B2-4c)的开平方)/2
void CGame::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
{
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hWnd, &pt);
Ray ray = CalculateRay(pt.x, pt.y);
D3DXMATRIX mat;
m_pDevice->GetTransform(D3DTS_VIEW, &mat);
D3DXMatrixInverse(&mat, NULL, &mat);
ray = TransformRay(ray, &mat);
for(int i=0; i<2; ++i)
{
if( CheckIntersection(&ray, &m_boundSphere[i]) )
m_bRotate[i] = true;
else
m_bRotate[i] = false;
}
}
break;
}
}
CGame::Ray CGame::CalculateRay(int x, int y)
{
float px=0.0f;
float py=0.0f;
//获取视口大小
D3DVIEWPORT9 vp;
m_pDevice->GetViewport(&vp);
//获取投影矩阵
D3DXMATRIX proj;
m_pDevice->GetTransform(D3DTS_PROJECTION,&proj);
//计算拾取射线
px=(((2.0f*x)/vp.Width)-1.0f)/proj._11;
py=(((-2.0f*y)/vp.Height)+1.0f)/proj._22;
Ray ray;
ray._origin = D3DXVECTOR3(0.0f,0.0f,0.0f);
ray._dirction = D3DXVECTOR3(px,py,1.0f);
return ray;
}
CGame::Ray CGame::TransformRay(Ray ray, D3DXMATRIX* T)
{
Ray transRay;
//转换射线的起点
D3DXVec3TransformCoord(&transRay._origin,&ray._origin,T);
//转换射线的方向
D3DXVec3TransformNormal(&transRay._dirction,&ray._dirction,T);
D3DXVec3Normalize(&transRay._dirction,&transRay._dirction);
return transRay;
}
BOOL CGame::CheckIntersection(Ray* ray, BoundSphere* sphere)
{
//计算t0与t1的值
D3DXVECTOR3 v = ray->_origin-sphere->_center;
float b = 2.0f*D3DXVec3Dot(&ray->_dirction,&v);
float c = D3DXVec3Dot(&v,&v)-(sphere->radius*sphere->radius);
float n = (b*b)-(4.0f*c);
if (n<0.0f)
{
return false;
}
n = sqrtf(n);
float t0 = (-b+n)/2.0f;
float t1 = (-b-n)/2.0f;
//判断是否相交
if (t0>=0||t1>=0)
{
return true;
}
return false;
}