在游戏中,鼠标拾取是不可或缺的。
一. 使用 D3DIntersect()选中模型Mesh
在D3D sample 中有一种方法,对鼠标轨迹线进行MVP 的逆变换,得到线在模型空间位置,然后使用 函数 D3DIntersect ,
和物体的位置进行比较,进而判断出是否是拾取到某个mesh
1. 获取当前的转换矩阵,包括 projection, view, 和 world
void detect_picking()
{
// get the current transform matrices
D3DXMATRIX matProjection, matView, matWorld;
d3ddev->GetTransform(D3DTS_PROJECTION, &matProjection);
d3ddev->GetTransform(D3DTS_VIEW, &matView);
d3ddev->GetTransform(D3DTS_WORLD, &matWorld);
}
2. 把鼠标的坐标转换为鼠标的角度
使用一条模型空间的线表示鼠标的移动拾取, 起点为原点,线为空间方向向量
在视锥内计算x, y 方向的角度
GetCursorPos(&MousePos);
float xAngle = (((2.0f * MousePos.x) / SCREEN_WIDTH) - 1.0f) / matProjection(0, 0);
float yAngle = (((-2.0f * MousePos.y) / SCREEN_HEIGHT) + 1.0f) / matProjection(1, 1);
3. 找到逆转换矩阵
void detect_picking(){ // get the current transform matrices D3DXMATRIX matProjection, matView, matWorld, matInverse; d3ddev->GetTransform(D3DTS_PROJECTION, &matProjection); d3ddev->GetTransform(D3DTS_VIEW, &matView); d3ddev->GetTransform(D3DTS_WORLD, &matWorld); // use the mouse coordinates to get the mouse angle GetCursorPos(&MousePos); float xAngle = (((2.0f * MousePos.x) / SCREEN_WIDTH) - 1.0f) / matProjection(0, 0); float yAngle = (((-2.0f * MousePos.y) / SCREEN_HEIGHT) + 1.0f) / matProjection(1, 1); D3DXVECTOR3 origin, direction; origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f); direction = D3DXVECTOR3(xAngle, yAngle, 1.0f); // find the inverse matrix D3DXMatrixInverse(&matInverse, NULL, &(matWorld * matView));}
}
4. 使用逆矩阵把鼠标的方向位置转化为模型空间位置
void detect_picking()
{
// get the current transform matrices
D3DXMATRIX matProjection, matView, matWorld, matInverse;
d3ddev->GetTransform(D3DTS_PROJECTION, &matProjection);
d3ddev->GetTransform(D3DTS_VIEW, &matView);
d3ddev->GetTransform(D3DTS_WORLD, &matWorld);
// use the mouse coordinates to get the mouse angle
GetCursorPos(&MousePos);
float xAngle = (((2.0f * MousePos.x) / SCREEN_WIDTH) - 1.0f) / matProjection(0, 0);
float yAngle = (((-2.0f * MousePos.y) / SCREEN_HEIGHT) + 1.0f) / matProjection(1, 1);
D3DXVECTOR3 origin, direction;
origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
direction = D3DXVECTOR3(xAngle, yAngle, 1.0f);
// find the inverse matrix
D3DXMatrixInverse(&matInverse, NULL, &(matWorld * matView));
// convert origin and direction into model space
D3DXVec3TransformCoord(&origin, &origin, &matInverse);
D3DXVec3TransformNormal(&direction, &direction, &matInverse);
D3DXVec3Normalize(&direction, &direction);
}
最后一步,判断交汇
HRESULT D3DXIntersect(LPD3DXBASEMESH pMesh, // pointer to the mesh to check
D3DXVECTOR3* pRayPos, // pointer to the origin
D3DXVECTOR3* pRayDir, // pointer to the direction
BOOL* pHit, // a boolean telling if the mesh was selected
DWORD* pFaceIndex, // tells which selected triangle was closest
FLOAT* pU, // tells the U coordinate of that triangle
FLOAT* pV, // tells the V coordinate of that triangle
FLOAT* pDist, // a value telling how far from the camera the face is
LPD3DXBUFFER* ppAllHits, // a buffer telling all picked faces
DWORD* pCountOfHits); // tells how many faces were picked
整个函数的完整版即如下:
void detect_picking()
{
// get the current transform matrices
D3DXMATRIX matProjection, matView, matWorld, matInverse;
d3ddev->GetTransform(D3DTS_PROJECTION, &matProjection);
d3ddev->GetTransform(D3DTS_VIEW, &matView);
d3ddev->GetTransform(D3DTS_WORLD, &matWorld);
// use the mouse coordinates to get the mouse angle
GetCursorPos(&MousePos);
float xAngle = (((2.0f * MousePos.x) / SCREEN_WIDTH) - 1.0f) / matProjection(0, 0);
float yAngle = (((-2.0f * MousePos.y) / SCREEN_HEIGHT) + 1.0f) / matProjection(1, 1);
D3DXVECTOR3 origin, direction;
origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
direction = D3DXVECTOR3(xAngle, yAngle, 1.0f);
// find the inverse matrix
D3DXMatrixInverse(&matInverse, NULL, &(matWorld * matView));
// convert origin and direction into model space
D3DXVec3TransformCoord(&origin, &origin, &matInverse);
D3DXVec3TransformNormal(&direction, &direction, &matInverse);
D3DXVec3Normalize(&direction, &direction);
// detect picking
BOOL hit;
D3DXIntersect(meshTeapot, &origin, &direction, &hit, NULL, NULL, NULL, NULL, NULL, NULL);
if(hit)
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);
else
d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);
}
二. 根据RTT制作查询表选中游戏中实体模型
实际游戏中,只需要选中某个实体即可,不必要精细到某个mesh,对活动可拾取目标渲染到一张屏幕大小的RTT,然后对该RTT 生成查询信息。
const ITexture* tex = pRenderSys->getRenderTargetTexture(renderTargetID);
PixelBox dstBox(w,h,1,PF_A8R8G8B8, mpData);
//tex->blitToMemory(Box(0,0,0,w,h,1), dstBox);
if( !tex->blitToMemory(Box(0,0,0,w,h,1), dstBox) )
{
memset(mpData, 0, sizeof(uint) * w * h );
}
鼠标移动到可拾取地方,即会有判断出拾取到某个可拾取物体, hittest 返回的是鼠标当前的renderable,即活动实体。
int Selector::rectHitTest( const Vector2& leftTop, const Vector2& rightBottom, std::vector<Result>& outList )
{
if(leftTop.x < 0 || leftTop.x > 1.0f || leftTop.y < 0 || leftTop.y > 1.0f)
return 0;
if(rightBottom.x < 0 || rightBottom.x > 1.0f || rightBottom.y < 0 || rightBottom.y > 1.0f)
return 0;
int w = sResoluctions[mLevel].x;
int h = sResoluctions[mLevel].y;
int lx = (int)(leftTop.x * w);
int ty = (int)(leftTop.y * h);
int rx = (int)(rightBottom.x * w);
int by = (int)(rightBottom.y * h);
lx = Clamp(lx, 0, w - 1);
ty = Clamp(ty, 0, h - 1);
rx = Clamp(rx, 0, w - 1);
by = Clamp(by, 0, h - 1);
std::set<uint> selSet;
for(int i = lx; i < rx; ++i)
{
for(int j = ty; j < by; ++j)
{
uint v = mpData[w * j + i];
if (v != 0 && selSet.find(v) == selSet.end())
{
Result rt;
fillResult(v, rt);
outList.push_back(rt);
selSet.insert(v);
}
}
}
return (int)outList.size();
}
Reference:
http://www.directxtutorial.com/Lesson.aspx?lessonid=9-8-4