1.鼠标拾取的实现
首先我们需要获取鼠标输入,并且要能够生成射线,使用基于射线检测的方法来实现鼠标拾取,注:鼠标拾取代码写在GameApp::UpdateScene函数中
1)第一步:获取鼠标输入和生成射线
//拾取检测并生成物体
//获取键鼠输入
ImGuiIO& io = ImGui::GetIO();
//获取鼠标在屏幕上位置,并限制范围
ImVec2 mousePos = ImGui::GetMousePos();
mousePos.x = std::clamp(mousePos.x, 0.0f, m_ClientWidth - 1.0f);
mousePos.y = std::clamp(mousePos.y, 0.0f, m_ClientHeight - 1.0f);
//生成射线
Ray mouseRay = Ray::ScreenToRay(*m_pCamera, mousePos.x, mousePos.y);
2)第二步:进行射线检测
bool isHit = false;//记录射线是否与物体相交
std::string pickedObjStr = "None";
if (mouseRay.Hit(m_Sphere.GetBoundingOrientedBox()))
{
pickedObjStr = "Sphere";
isHit = true;
}
else if (mouseRay.Hit(m_Ground.GetBoundingOrientedBox()))
{
pickedObjStr = "Ground";
isHit = true;
}
else if (mouseRay.Hit(m_Cylinder.GetBoundingOrientedBox()))
{
pickedObjStr = "Cylinder";
isHit = true;
}
接着再imgui里加入这样一条代码显示当前拾取物体
ImGui::Text("Current Object: %s", pickedObjStr.c_str());
下面是射线类(Ray)的定义,这里我们用到了ScreenToRay方法来将鼠标的屏幕坐标转换为一条射线,并在射线检测时使用Hit方法判断射线是否与物体的包围盒相交
struct Ray
{
Ray();
Ray(const DirectX::XMFLOAT3& origin, const DirectX::XMFLOAT3& direction);
static Ray ScreenToRay(const Camera& camera, float screenX, float screenY);
bool Hit(const DirectX::BoundingBox& box, float* pOutDist = nullptr, float maxDist = FLT_MAX);
bool Hit(const DirectX::BoundingOrientedBox& box, float* pOutDist = nullptr, float maxDist = FLT_MAX);
bool Hit(const DirectX::BoundingSphere& sphere, float* pOutDist = nullptr, float maxDist = FLT_MAX);
bool XM_CALLCONV Hit(DirectX::FXMVECTOR V0, DirectX::FXMVECTOR V1, DirectX::FXMVECTOR V2, float* pOutDist = nullptr, float maxDist = FLT_MAX);
DirectX::XMFLOAT3 origin; // 射线原点
DirectX::XMFLOAT3 direction; // 单位方向向量
};
2.鼠标操作放置方块
再在GameApp::UpdateScene中添加按下鼠标左键实现在摄像机前生成方块功能
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
// 点击鼠标左键生成立方体
XMFLOAT3 putPos = m_pCamera->GetPosition();
XMFLOAT3 putDir = m_pCamera->GetLookAxis();
Model* pModel = m_ModelManager.CreateFromGeometry("Cube", Geometry::CreateBox(),false);
pModel->SetDebugObjectName("Cube");
m_TextureManager.CreateFromFile("..\\Texture\\stone.dds");
pModel->materials[0].Set<std::string>("$Diffuse", "..\\Texture\\stone.dds");
pModel->materials[0].Set<XMFLOAT4>("$AmbientColor", XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f));
pModel->materials[0].Set<XMFLOAT4>("$DiffuseColor", XMFLOAT4(0.6f, 0.6f, 0.6f, 1.0f));
pModel->materials[0].Set<XMFLOAT4>("$SpecularColor", XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f));
pModel->materials[0].Set<float>("$SpecularPower", 16.0f);
pModel->materials[0].Set<XMFLOAT4>("$ReflectColor", XMFLOAT4());
m_Cube[i].SetModel(pModel);
m_Cube[i].GetTransform().SetPosition(putDir.x * 6.0f + putPos.x, putDir.y * 6.0f + putPos.y, putDir.z * 6.0f + putPos.z);
i++;
if (i >= 100)//判断是否超出最大值
i = 0;
}
这里我设置i为全局变量,将立方体的最大生成数设为100个
还需要在GameApp::DrawScene函数中用Draw方法绘制生成的立方体
for (int j = 0;j < i;j++)
{
m_Cube[j].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
}
现在就可以补充鼠标拾取生成的方块的代码了
for (int m = 0;m < i;m++)
{
if (mouseRay.Hit(m_Cube[m].GetBoundingOrientedBox()))
{
pickedObjStr = "Cube";
isHit = true;
}
}
3.鼠标操作销毁方块
这里我采取的方式是通过修改m_Cube数组使得在调用GameApp::DrawScene函数时,函数内部的Draw函数不会绘制销毁的方块。
在GameApp::UpdateScene中加上按下鼠标右键销毁方块功能
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))
{
for (int k = 0;k < i;k++)
{
if (mouseRay.Hit(m_Cube[k].GetBoundingOrientedBox())&& k!=i )
{
m_Cube[k] = m_Cube[k + 1];//修改m_Cube数组
i--;
}
}
}
4.效果展示
1)鼠标拾取:
2)方块生成与销毁: