绘制四棱锥
首先将四棱锥顶点设置如下:
按照左手坐标系,各索引对应的顶点为:
索引0:(-1,-1,-1)
索引1:(0,1,0)
索引2:(1,-1,-1)
索引3:(-1,-1,1)
索引4:(1,-1,1)
则在GameApp.cpp文件中设置顶点如下:
VertexPosColor vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};
然后设置索引数组:
DWORD indices[] = {
// 正面
0, 1, 2,
// 左面
3, 1, 0,
// 背面
4, 1, 3,
// 右面
2, 1, 4,
// 底面
3, 0, 2,
2, 4, 3
};
最后修改DrawIndexed函数的参数,就可以显示一个四棱锥了。
键鼠配置准备工作
先将Mouse类和Keyboard类相关的文件Mouse.h,Mouse.cpp,Keyboard.h和Keyboard.cpp四个文件复制粘贴放入项目文件的文件夹中,再用cmake配置。
键鼠变量配置
准备工作完成之后打开cmake生成的项目,在d3dApp.h中添加预编译指令
#include "Mouse.h"
#include "Keyboard.h"
接着在类中声明键盘和鼠标的变量如下:
// 键鼠输入
std::unique_ptr<DirectX::Mouse> m_pMouse; // 鼠标
DirectX::Mouse::ButtonStateTracker m_MouseTracker; // 鼠标状态追踪器
std::unique_ptr<DirectX::Keyboard> m_pKeyboard; // 键盘
DirectX::Keyboard::KeyboardStateTracker m_KeyboardTracker; // 键盘状态追踪器
然后切换到GameApp.cpp文件中,初始化鼠标指针
// 初始化鼠标,键盘不需要
m_pMouse->SetWindow(m_hMainWnd);
m_pMouse->SetMode(DirectX::Mouse::MODE_ABSOLUTE);
这里使用SetWindow函数绑定窗口句柄,用SetMode函数设置鼠标模式为绝对坐标模式MODE_ABSOLUTE。
这时用来操作鼠标和键盘的变量已经初始化好了,但是要使键盘鼠标能够对我们的操作做出回应,还需要设置一下回调函数,其名为MsgProc。
设置回调函数MsgProc
让我们回到d3dApp.cpp文件中,值得注意的是Mouse类和Keyboard类已经为我们提供了对操作做出反应的函数ProcessMessage,具体代码如下:
void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
auto pImpl = Impl::s_mouse;
if (!pImpl)
return;
HANDLE evts[3];
evts[0] = pImpl->mScrollWheelValue.get();
evts[1] = pImpl->mAbsoluteMode.get();
evts[2] = pImpl->mRelativeMode.get();
switch (WaitForMultipleObjectsEx(_countof(evts), evts, FALSE, 0, FALSE))
{
case WAIT_OBJECT_0:
pImpl->mState.scrollWheelValue = 0;
ResetEvent(evts[0]);
break;
case (WAIT_OBJECT_0 + 1):
{
pImpl->mMode = MODE_ABSOLUTE;
ClipCursor(nullptr);
POINT point;
point.x = pImpl->mLastX;
point.y = pImpl->mLastY;
// We show the cursor before moving it to support Remote Desktop
ShowCursor(TRUE);
if (MapWindowPoints(pImpl->mWindow, nullptr, &point, 1))
{
SetCursorPos(point.x, point.y);
}
pImpl->mState.x = pImpl->mLastX;
pImpl->mState.y = pImpl->mLastY;
}
break;
case (WAIT_OBJECT_0 + 2):
{
ResetEvent(pImpl->mRelativeRead.get());
pImpl->mMode = MODE_RELATIVE;
pImpl->mState.x = pImpl->mState.y = 0;
pImpl->mRelativeX = INT32_MAX;
pImpl->mRelativeY = INT32_MAX;
ShowCursor(FALSE);
pImpl->ClipToWindow();
}
break;
case WAIT_FAILED:
throw std::exception("WaitForMultipleObjectsEx");
}
switch (message)
{
case WM_ACTIVATEAPP:
ProcessMessage(message, wParam, lParam);
if (wParam)
{
pImpl->mInFocus = true;
if (pImpl->mMode == MODE_RELATIVE)
{
pImpl->mState.x = pImpl->mState.y = 0;
ShowCursor(FALSE);
pImpl->ClipToWindow();
}
}
else
{
int scrollWheel = pImpl->mState.scrollWheelValue;
memset(&pImpl->mState, 0, sizeof(State));
pImpl->mState.scrollWheelValue = scrollWheel;
pImpl->mInFocus = false;
}
return;
case WM_INPUT:
if (pImpl->mInFocus && pImpl->mMode == MODE_RELATIVE)
{
RAWINPUT raw;
UINT rawSize = sizeof(raw);
UINT resultData = GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
if (resultData == UINT(-1))
{
throw std::exception("GetRawInputData");
}
if (raw.header.dwType == RIM_TYPEMOUSE)
{
if (!(raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE))
{
pImpl->mState.x = raw.data.mouse.lLastX;
pImpl->mState.y = raw.data.mouse.lLastY;
ResetEvent(pImpl->mRelativeRead.get());
}
else if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP)
{
// This is used to make Remote Desktop sessons work
const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int x = static_cast<int>((float(raw.data.mouse.lLastX) / 65535.0f) * width);
int y = static_cast<int>((float(raw.data.mouse.lLastY) / 65535.0f) * height);
if (pImpl->mRelativeX == INT32_MAX)
{
pImpl->mState.x = pImpl->mState.y = 0;
}
else
{
pImpl->mState.x = x - pImpl->mRelativeX;
pImpl->mState.y = y - pImpl->mRelativeY;
}
pImpl->mRelativeX = x;
pImpl->mRelativeY = y;
ResetEvent(pImpl->mRelativeRead.get());
}
}
}
return;
case WM_MOUSEMOVE:
break;
case WM_LBUTTONDOWN:
pImpl->mState.leftButton = true;
break;
case WM_LBUTTONUP:
pImpl->mState.leftButton = false;
break;
case WM_RBUTTONDOWN:
pImpl->mState.rightButton = true;
break;
case WM_RBUTTONUP:
pImpl->mState.rightButton = false;
break;
case WM_MBUTTONDOWN:
pImpl->mState.middleButton = true;
break;
case WM_MBUTTONUP:
pImpl->mState.middleButton = false;
break;
case WM_MOUSEWHEEL:
pImpl->mState.scrollWheelValue += GET_WHEEL_DELTA_WPARAM(wParam);
return;
case WM_XBUTTONDOWN:
switch (GET_XBUTTON_WPARAM(wParam))
{
case XBUTTON1:
pImpl->mState.xButton1 = true;
break;
case XBUTTON2:
pImpl->mState.xButton2 = true;
break;
}
break;
case WM_XBUTTONUP:
switch (GET_XBUTTON_WPARAM(wParam))
{
case XBUTTON1:
pImpl->mState.xButton1 = false;
break;
case XBUTTON2:
pImpl->mState.xButton2 = false;
break;
}
break;
case WM_MOUSEHOVER:
break;
default:
// Not a mouse message, so exit
return;
}
if (pImpl->mMode == MODE_ABSOLUTE)
{
// All mouse messages provide a new pointer position
int xPos = static_cast<short>(LOWORD(lParam)); // GET_X_LPARAM(lParam);
int yPos = static_cast<short>(HIWORD(lParam)); // GET_Y_LPARAM(lParam);
pImpl->mState.x = pImpl->mLastX = xPos;
pImpl->mState.y = pImpl->mLastY = yPos;
}
}
void Keyboard::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
auto pImpl = Impl::s_keyboard;
if (!pImpl)
return;
bool down = false;
switch (message)
{
case WM_ACTIVATEAPP:
pImpl->Reset();
return;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
down = true;
break;
case WM_KEYUP:
case WM_SYSKEYUP:
break;
default:
return;
}
int vk = static_cast<int>(wParam);
switch (vk)
{
case VK_SHIFT:
vk = MapVirtualKey((lParam & 0x00ff0000) >> 16, MAPVK_VSC_TO_VK_EX);
if (!down)
{
// Workaround to ensure left vs. right shift get cleared when both were pressed at same time
KeyUp(VK_LSHIFT, pImpl->mState);
KeyUp(VK_RSHIFT, pImpl->mState);
}
break;
case VK_CONTROL:
vk = (lParam & 0x01000000) ? VK_RCONTROL : VK_LCONTROL;
break;
case VK_MENU:
vk = (lParam & 0x01000000) ? VK_RMENU : VK_LMENU;
break;
}
if (down)
{
KeyDown(vk, pImpl->mState);
}
else
{
KeyUp(vk, pImpl->mState);
}
}
因此,我们要做的只有在回调函数中把相应的按键情况作出反应的语句中添加调用ProcessMessage函数的语句
LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
// WM_ACTIVATE is sent when the window is activated or deactivated.
// We pause the game when the window is deactivated and unpause it
// when it becomes active.
case WM_ACTIVATE:
if (LOWORD(wParam) == WA_INACTIVE)
{
m_AppPaused = true;
m_Timer.Stop();
}
else
{
m_AppPaused = false;
m_Timer.Start();
}
return 0;
// WM_SIZE is sent when the user resizes the window.
case WM_SIZE:
// Save the new client area dimensions.
m_ClientWidth = LOWORD(lParam);
m_ClientHeight = HIWORD(lParam);
if (m_pd3dDevice)
{
if (wParam == SIZE_MINIMIZED)
{
m_AppPaused = true;
m_Minimized = true;
m_Maximized = false;
}
else if (wParam == SIZE_MAXIMIZED)
{
m_AppPaused = false;
m_Minimized = false;
m_Maximized = true;
OnResize();
}
else if (wParam == SIZE_RESTORED)
{
// Restoring from minimized state?
if (m_Minimized)
{
m_AppPaused = false;
m_Minimized = false;
OnResize();
}
// Restoring from maximized state?
else if (m_Maximized)
{
m_AppPaused = false;
m_Maximized = false;
OnResize();
}
else if (m_Resizing)
{
// If user is dragging the resize bars, we do not resize
// the buffers here because as the user continuously
// drags the resize bars, a stream of WM_SIZE messages are
// sent to the window, and it would be pointless (and slow)
// to resize for each WM_SIZE message received from dragging
// the resize bars. So instead, we reset after the user is
// done resizing the window and releases the resize bars, which
// sends a WM_EXITSIZEMOVE message.
}
else // API call such as SetWindowPos or m_pSwapChain->SetFullscreenState.
{
OnResize();
}
}
}
return 0;
// WM_EXITSIZEMOVE is sent when the user grabs the resize bars.
case WM_ENTERSIZEMOVE:
m_AppPaused = true;
m_Resizing = true;
m_Timer.Stop();
return 0;
// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
// Here we reset everything based on the new window dimensions.
case WM_EXITSIZEMOVE:
m_AppPaused = false;
m_Resizing = false;
m_Timer.Start();
OnResize();
return 0;
// WM_DESTROY is sent when the window is being destroyed.
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// The WM_MENUCHAR message is sent when a menu is active and the user presses
// a key that does not correspond to any mnemonic or accelerator key.
case WM_MENUCHAR:
// Don't beep when we alt-enter.
return MAKELRESULT(0, MNC_CLOSE);
// Catch this message so to prevent the window from becoming too small.
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;
case WM_INPUT:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_MOUSEMOVE:
m_pMouse->ProcessMessage(msg, wParam, lParam);//处理鼠标信息
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
m_pKeyboard->ProcessMessage(msg, wParam, lParam);//鼠标键盘信息的处理
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
这里我们只需在
WM_ACTIVATEAPP
WM_INPUT
WM_LBUTTONDOWN
WM_MBUTTONDOWN
WM_RBUTTONDOWN
WM_XBUTTONDOWN
WM_LBUTTONUP
WM_MBUTTONUP
WM_RBUTTONUP
WM_XBUTTONUP
WM_MOUSEWHEEL
WM_MOUSEHOVER
WM_MOUSEMOVE
这些情况下调用ProcessMessage函数即可。
现在,我们进行键鼠的操作已经能够得到反应,但是我们还有最后一件事要做,就是决定按下哪些按键,执行哪些操作才做出反应,做什么反应。
设置四棱锥旋转
1. 鼠标操作
对于鼠标,我将其设置为按住鼠标左键并拖动,使四棱锥可以按拖动的方向旋转,具体代码如下:
首先我们要获取鼠标状态
//获取鼠标状态
Mouse::State mouseState = m_pMouse->GetState();
Mouse::State lastMouseState = m_MouseTracker.GetLastState();
不要忘了用Update函数更新鼠标状态
// 更新鼠标按钮状态
m_MouseTracker.Update(mouseState);
然后再设置旋转
if (mouseState.leftButton == true && m_MouseTracker.leftButton == m_MouseTracker.HELD)
{
// 旋转立方体
theta -= (mouseState.x - lastMouseState.x) * 0.01f;
phi -= (mouseState.y - lastMouseState.y) * 0.01f;
}
m_CBuffer.world = XMMatrixRotationY(theta) * XMMatrixRotationX(phi);
2.键盘操作
对于键盘,我采用WASD四个按键分别实现四棱锥以自身上下左右旋转的操作
同样我们也要获取键盘状态
//获取键盘状态
Keyboard::State keyState = m_pKeyboard->GetState();
Keyboard::State lastKeyState = m_KeyboardTracker.GetLastState();
接着更新键盘状态
//更新键盘按钮状态
m_KeyboardTracker.Update(keyState);
最后分别WASD的旋转操作
if (keyState.IsKeyDown(Keyboard::W))
{
phi += dt * 3;
}
if (keyState.IsKeyDown(Keyboard::S))
{
phi -= dt * 3;
}
if (keyState.IsKeyDown(Keyboard::A))
{
theta += dt * 3;
}
if (keyState.IsKeyDown(Keyboard::D))
{
theta -= dt * 3;
}
m_CBuffer.world = XMMatrixRotationY(theta) * XMMatrixRotationX(phi);
至此,我们的键鼠操作四棱锥旋转就已经完成了,效果如下:
DX11键鼠操作四棱锥
注:这里我使用的项目文件原型XjunDX11教程项目文件中的03 Rendering a Cube。