在实际应用中,常常使用包围球或者包围盒代替物体与拾取射线进行相交测试,假设拾取射线与包围盒或者包围球相交,就认为拾取射线与物体相交了,其实,在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;}