本例子是关于用鼠标选取物体的。
1OnCreateDevice
// Load the mesh with D3DX and get back a ID3DXMesh*. For this
// sample we'll ignore the X file's embedded materials since we know
// exactly the model we're loading. See the mesh samples such as
// "OptimizedMesh" for a more generic mesh loading example.
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH, TEXT( "scanner\\scannerarm.x" ) ) );
V_RETURN( g_Mesh.Create( pd3dDevice, str ) );
V_RETURN( g_Mesh.SetFVF( pd3dDevice, D3DVERTEX::FVF ) );
载入网格,设置FVF,这里的变量可以直接用FVF描述,可以不用描述数组。
// Create the vertex buffer
if( FAILED( pd3dDevice->CreateVertexBuffer( 3 * MAX_INTERSECTIONS * sizeof( D3DVERTEX ),
D3DUSAGE_WRITEONLY, D3DVERTEX::FVF,
D3DPOOL_MANAGED, &g_pVB, NULL ) ) )
{
return E_FAIL;
}
创建一个三角形的顶点缓冲区,用途就是显示选中的第一个三角形。
这一次跟效果文件没有什么关系,所以就不列出代码了。
2在OnRenderFrame函数中,调用Pick(),计算出选到的三角形。
// Get the Pick ray from the mouse position
if( GetCapture() )
{
const D3DXMATRIX* pmatProj = g_Camera.GetProjMatrix();
POINT ptCursor;
GetCursorPos( &ptCursor );
ScreenToClient( DXUTGetHWND(), &ptCursor );
用GetCapture函数得到鼠标所在窗口的句柄,然后用GetCursorPos和ScreenToClient得到鼠标在窗口中的坐标。
// Compute the vector of the Pick ray in screen space
D3DXVECTOR3 v;
v.x = ( ( ( 2.0f * ptCursor.x ) / pd3dsdBackBuffer->Width ) - 1 ) / pmatProj->_11;
v.y = -( ( ( 2.0f * ptCursor.y ) / pd3dsdBackBuffer->Height ) - 1 ) / pmatProj->_22;
v.z = 1.0f;
计算出点在投影窗口中的坐标,并加上z坐标,就成了拾取向量。
// Get the inverse view matrix
const D3DXMATRIX matView = *g_Camera.GetViewMatrix();
const D3DXMATRIX matWorld = *g_Camera.GetWorldMatrix();
D3DXMATRIX mWorldView = matWorld * matView;
D3DXMATRIX m;
D3DXMatrixInverse( &m, NULL, &mWorldView );
// Transform the screen space Pick ray into 3D space
vPickRayDir.x = v.x * m._11 + v.y * m._21 + v.z * m._31;
vPickRayDir.y = v.x * m._12 + v.y * m._22 + v.z * m._32;
vPickRayDir.z = v.x * m._13 + v.y * m._23 + v.z * m._33;
vPickRayOrig.x = m._41;
vPickRayOrig.y = m._42;
vPickRayOrig.z = m._43;
再将拾取向量和拾取向量原点转换到3D空间去。这里的算法貌似跟龙书第一版的有一点不同,不过能用就好了。
// Get the Picked triangle
if( GetCapture() )
{
LPD3DXMESH pMesh;
g_Mesh.GetMesh()->CloneMeshFVF( D3DXMESH_MANAGED,
g_Mesh.GetMesh()->GetFVF(), pD3Device, &pMesh );
接下来开始获取拾取到的三角形,首先将网格拷贝一份放在内存中,便于处理。
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
pMesh->GetVertexBuffer( &pVB );
pMesh->GetIndexBuffer( &pIB );
WORD* pIndices;
D3DVERTEX* pVertices;
pIB->Lock( 0, 0, ( void** )&pIndices, 0 );
pVB->Lock( 0, 0, ( void** )&pVertices, 0 );
得到VB和IB,并锁定。
if( g_bUseD3DXIntersect )
{
// When calling D3DXIntersect, one can get just the closest intersection and not
// need to work with a D3DXBUFFER. Or, to get all intersections between the ray and
// the mesh, one can use a D3DXBUFFER to receive all intersections. We show both
// methods.
if( !g_bAllHits )
{
// Collect only the closest intersection
BOOL bHit;
DWORD dwFace;
FLOAT fBary1, fBary2, fDist;
D3DXIntersect( pMesh, &vPickRayOrig, &vPickRayDir, &bHit, &dwFace, &fBary1, &fBary2, &fDist,
NULL, NULL );
if( bHit )
{
g_dwNumIntersections = 1;
g_IntersectionArray[0].dwFace = dwFace;
g_IntersectionArray[0].fBary1 = fBary1;
g_IntersectionArray[0].fBary2 = fBary2;
g_IntersectionArray[0].fDist = fDist;
}
else
{
g_dwNumIntersections = 0;
}
}
第一段检测拾取的代码,注意这里一共四种情况:用不用D3DXIntersect和检测一个还是检测所有。
else
{
// Collect all intersections
BOOL bHit;
LPD3DXBUFFER pBuffer = NULL;
D3DXINTERSECTINFO* pIntersectInfoArray;
if( FAILED( hr = D3DXIntersect( pMesh, &vPickRayOrig, &vPickRayDir, &bHit, NULL, NULL, NULL, NULL,
&pBuffer, &g_dwNumIntersections ) ) )
{
SAFE_RELEASE( pMesh );
SAFE_RELEASE( pVB );
SAFE_RELEASE( pIB );
return hr;
}
if( g_dwNumIntersections > 0 )
{
pIntersectInfoArray = ( D3DXINTERSECTINFO* )pBuffer->GetBufferPointer();
if( g_dwNumIntersections > MAX_INTERSECTIONS )
g_dwNumIntersections = MAX_INTERSECTIONS;
for( DWORD iIntersection = 0; iIntersection < g_dwNumIntersections; iIntersection++ )
{
g_IntersectionArray[iIntersection].dwFace = pIntersectInfoArray[iIntersection].FaceIndex;
g_IntersectionArray[iIntersection].fBary1 = pIntersectInfoArray[iIntersection].U;
g_IntersectionArray[iIntersection].fBary2 = pIntersectInfoArray[iIntersection].V;
g_IntersectionArray[iIntersection].fDist = pIntersectInfoArray[iIntersection].Dist;
}
}
SAFE_RELEASE( pBuffer );
}
}
第二段。这一次使用D3DXIntersect检测出所有拾取到的三角形。
else
{
// Not using D3DX
DWORD dwNumFaces = g_Mesh.GetMesh()->GetNumFaces();
FLOAT fBary1, fBary2;
FLOAT fDist;
for( DWORD i = 0; i < dwNumFaces; i++ )
{
D3DXVECTOR3 v0 = pVertices[pIndices[3 * i + 0]].p;
D3DXVECTOR3 v1 = pVertices[pIndices[3 * i + 1]].p;
D3DXVECTOR3 v2 = pVertices[pIndices[3 * i + 2]].p;
// Check if the Pick ray passes through this point
if( IntersectTriangle( vPickRayOrig, vPickRayDir, v0, v1, v2,
&fDist, &fBary1, &fBary2 ) )
{
if( g_bAllHits || g_dwNumIntersections == 0 || fDist < g_IntersectionArray[0].fDist )
{
if( !g_bAllHits )
g_dwNumIntersections = 0;
g_IntersectionArray[g_dwNumIntersections].dwFace = i;
g_IntersectionArray[g_dwNumIntersections].fBary1 = fBary1;
g_IntersectionArray[g_dwNumIntersections].fBary2 = fBary2;
g_IntersectionArray[g_dwNumIntersections].fDist = fDist;
g_dwNumIntersections++;
if( g_dwNumIntersections == MAX_INTERSECTIONS )
break;
}
}
}
}
不使用提供的API,自己进行检测,逐个三角形进行检测。包括了是否输出所有拾取三角形两种情况。注意IntersectTriangle是自己编写的函数,检测是否射线是否和三角形相交,代码比较简单,就不单独列出了。
if( g_dwNumIntersections > 0 )
{
D3DVERTEX* v;
D3DVERTEX* vThisTri;
WORD* iThisTri;
D3DVERTEX v1, v2, v3;
INTERSECTION* pIntersection;
g_pVB->Lock( 0, 0, ( void** )&v, 0 );
for( DWORD iIntersection = 0; iIntersection < g_dwNumIntersections; iIntersection++ )
{
pIntersection = &g_IntersectionArray[iIntersection];
vThisTri = &v[iIntersection * 3];
iThisTri = &pIndices[3 * pIntersection->dwFace];
// get vertices hit
vThisTri[0] = pVertices[iThisTri[0]];
vThisTri[1] = pVertices[iThisTri[1]];
vThisTri[2] = pVertices[iThisTri[2]];
// If all you want is the vertices hit, then you are done. In this sample, we
// want to show how to infer texture coordinates as well, using the BaryCentric
// coordinates supplied by D3DXIntersect
FLOAT dtu1 = vThisTri[1].tu - vThisTri[0].tu;
FLOAT dtu2 = vThisTri[2].tu - vThisTri[0].tu;
FLOAT dtv1 = vThisTri[1].tv - vThisTri[0].tv;
FLOAT dtv2 = vThisTri[2].tv - vThisTri[0].tv;
pIntersection->tu = vThisTri[0].tu + pIntersection->fBary1 * dtu1 + pIntersection->fBary2 * dtu2;
pIntersection->tv = vThisTri[0].tv + pIntersection->fBary1 * dtv1 + pIntersection->fBary2 * dtv2;
}
g_pVB->Unlock();
}
接下来对检测到的三角形进行处理。
3回到渲染函数,此时已经得到拾取三角形的信息。
V( g_pEffect->Begin( &uPasses, 0 ) );
// Set render mode to lit, solid triangles
pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
// If a triangle is Picked, draw it
if( g_dwNumIntersections > 0 )
{
for( UINT uPass = 0; uPass < uPasses; ++uPass )
{
V( g_pEffect->BeginPass( uPass ) );
// Draw the Picked triangle
pd3dDevice->SetFVF( D3DVERTEX::FVF );
pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( D3DVERTEX ) );
pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, g_dwNumIntersections );
V( g_pEffect->EndPass() );
}
// Set render mode to unlit, wireframe triangles
pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
}
V( g_pEffect->End() );
// Render the mesh
V( g_Mesh.Render( g_pEffect ) );
注意这里先将FILLMODE设置成SOLID,假如有拾取三角形,则修改成WIREFRAME。还可以看到这里的效果框架只应用到了三角形上。