Search code examples
c++windowsdirectx-11

Cube Rotation Only Triggered by Mouse Movement or Key Press


Edit : well I forgot to include some related functions that make it difficult for you to identify the error. I'm sorry about this. So I have updated my question again

I'm encountering a peculiar issue with the rotation of a cube in my application. The cube only rotates when I move the mouse or press a key, and I'm looking to have continuous rotation without relying on user input.

Details:

Mouse Movement: The cube rotates correctly when I move the mouse, but I need it to rotate continuously without the need for constant mouse movement. Key Press: Pressing a key rotates the cube momentarily. However, I want the rotation to continue as long as the key is held down. Window Close Button: Clicking the window's close button (X button) doesn't close the application window as expected. Expectations:

Continuous rotation of the cube without requiring constant mouse movement or key presses. Proper functionality of the window close button to close the application window. Current Implementation:

Cube rotation is handled within the Render function, triggered by mouse movement and key presses. The application loop (while loop) processes input events using PeekMessage and GetMessage functions.

Main.cpp

#include "WindowManager.h"
#include "EngineGraphics.h"

ID3D11Device* gp_Device;
ID3D11DeviceContext* gp_DeviceContext;

IDXGIDevice* gp_DXGI_Device;
IDXGIAdapter* gp_DXGI_Adapter;
IDXGIFactory* gp_DXGI_Factory;
IDXGISwapChain* gp_DXGI_SwapChain;

ID3D11VertexShader* gp_VS;
ID3D11PixelShader* gp_PS;
ID3D11InputLayout* gp_InputLayout;
ID3D11Buffer* gp_VBuffer;
ID3D11Buffer* gp_IBuffer;
ID3D11RasterizerState* gp_rs;

ID3D11RenderTargetView* gp_BackBuffer;

float input;

Vertex Cube[] = {
    { XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
    { XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.5f, 0.5f, -0.5f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
    { XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
    // Back face
    { XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
    { XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};
WORD CubeIndices[] = {
    // Front face
    0, 1, 2,
    0, 2, 3,
    // Back face
    4, 5, 6,
    4, 6, 7,
    // Left face
    4, 7, 1,
    4, 1, 0,
    // Right face
    3, 2, 6,
    3, 6, 5,
    // Top face
    1, 7, 6,
    1, 6, 2,
    // Bottom face
    4, 0, 3,
    4, 3, 5
};

void InitD3D(HWND& hWnd, RECT& wr);
void InitPipeline();
void InitGraphics();
void Render();
void CleanD3D();

using namespace ExtroEngine;

LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HWND hWnd;

    UINT style = CS_HREDRAW | CS_VREDRAW;
    WindowManager::InitWindowExW(style, WinProc, 0, 0, hInstance, L"Scene", NULL, IDC_ARROW, NULL, NULL, CreateSolidBrush(RGB(0, 0, 0)));

    hWnd = CreateWindowExW(NULL, L"Scene", L"Scene", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 600, 600,
        NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);

    RECT wr = { 0, 0, SCENE_WIDTH, SCENE_HEIGHT };
    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);

    InitD3D(hWnd, wr);

    MSG msg = { 0 };
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                break;
        }
        Render();
    }
    CleanD3D();
    return 0;
}
void InitD3D(HWND& hWnd, RECT& wr)
{
    HRESULT hr = 0;
    GetClientRect(hWnd, &wr);
    D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_10_1 };
    D3D_DRIVER_TYPE DriveType[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE };

    DXGI_SWAP_CHAIN_DESC swpDesc;
    ZeroMemory(&swpDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
    swpDesc.BufferCount = 1;
    swpDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swpDesc.BufferDesc.Width = SCENE_WIDTH_MAX_HD;
    swpDesc.BufferDesc.Height = SCENE_HEIGHT_MAX_HD;
    swpDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swpDesc.OutputWindow = hWnd;
    swpDesc.SampleDesc.Count = 1;
    swpDesc.SampleDesc.Quality = 0;
    swpDesc.Windowed = TRUE;

    for (auto DriveSelect : DriveType)
    {
        hr = D3D11CreateDeviceAndSwapChain(NULL, DriveSelect, NULL, NULL, NULL, NULL,
            D3D11_SDK_VERSION, &swpDesc, &gp_DXGI_SwapChain, &gp_Device, featureLevels, &gp_DeviceContext);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }
    if (FAILED(hr)) {
        MessageBox(NULL, L"Error Create Device And Swap Chain", L"Error", MB_OK);
        CleanD3D();
        return;
    }

    ID3D11Texture2D* pBackBuffer;
    gp_DXGI_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

    gp_Device->CreateRenderTargetView(pBackBuffer, NULL, &gp_BackBuffer);
    pBackBuffer->Release();

    gp_DeviceContext->OMSetRenderTargets(1, &gp_BackBuffer, NULL);

    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = SCENE_WIDTH_MAX_HD;
    viewport.Height = SCENE_HEIGHT_MAX_HD;

    gp_DeviceContext->RSSetViewports(1, &viewport);

    InitPipeline();
    InitGraphics();
}
// -------------  KHỞI TẠO BỘ BIÊN DỊCH ĐỒ HOẠ
void InitPipeline()
{
    HRESULT hr;

    ID3DBlob* VS, * PS;
    hr = D3DCompileFromFile(L"ColorVPS.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
        "VShader", "vs_5_0", 0, 0, &VS, NULL);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Compile From File {ColorVPS.hlsl} Error With {VShader}",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    hr = D3DCompileFromFile(L"ColorVPS.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
        "PShader", "ps_5_0", 0, 0, &PS, NULL);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Compile From File {ColorVPS.hlsl} Error With {PShader}",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    hr = gp_Device->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &gp_VS);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Create Vertex Shader Error With {VS}",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    hr = gp_Device->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &gp_PS);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Create Pixel Shader Error With {PS}",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    gp_DeviceContext->VSSetShader(gp_VS, NULL, 0);
    gp_DeviceContext->PSSetShader(gp_PS, NULL, 0);

    D3D11_INPUT_ELEMENT_DESC InputShader[] =
    {
        {"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
        {"COLOR",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
    };
    gp_Device->CreateInputLayout(InputShader, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &gp_InputLayout);
    gp_DeviceContext->IASetInputLayout(gp_InputLayout);
}

// -------------- KHỞI TẠO ĐỒ HOẠ 
void InitGraphics() {
    HRESULT hr;

    // Tạo và gán giá trị cho Vertex Buffer (gp_VBuffer)
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));
    bd.Usage = D3D11_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(Vertex) * ARRAYSIZE(Cube);
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = Cube;

    hr = gp_Device->CreateBuffer(&bd, &InitData, &gp_VBuffer);
    if (!SUCCEEDED(hr)) {
        MessageBox(NULL, L"Create Vertex Buffer Error",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    // Tạo và gán giá trị cho Index Buffer (gp_IBuffer)
    D3D11_BUFFER_DESC ib = {};
    ib.Usage = D3D11_USAGE_IMMUTABLE;
    ib.ByteWidth = sizeof(WORD) * ARRAYSIZE(CubeIndices);
    ib.BindFlags = D3D11_BIND_INDEX_BUFFER;

    D3D11_SUBRESOURCE_DATA initData = {};
    initData.pSysMem = CubeIndices;

    hr = gp_Device->CreateBuffer(&ib, &initData, &gp_IBuffer);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Create Index Buffer Error",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }

    // Tạo và thiết lập Rasterizer State (gp_rs)
    D3D11_RASTERIZER_DESC rasterizerDesc = {};
    rasterizerDesc.FillMode = D3D11_FILL_SOLID;
    rasterizerDesc.CullMode = D3D11_CULL_BACK; // or whatever cull mode you need

    hr = gp_Device->CreateRasterizerState(&rasterizerDesc, &gp_rs);
    if (FAILED(hr)) {
        MessageBox(NULL, L"Create Rasterizer State Error",
            L"Error", MB_OK);
        CleanD3D();
        return;
    }
}

//-------------------VẼ CỬA SỔ-------------------//
void Render()
{
    float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
    gp_DeviceContext->ClearRenderTargetView(gp_BackBuffer, clearColor);

    gp_DeviceContext->VSSetShader(gp_VS, NULL, 0);
    gp_DeviceContext->PSSetShader(gp_PS, NULL, 0);
    gp_DeviceContext->IASetInputLayout(gp_InputLayout);
    gp_DeviceContext->OMSetRenderTargets(1, &gp_BackBuffer, NULL);

///////////////////// THIS ///////////////////////////////
    input = Input::GetKeyRaw(K);

    for (int i = 0; i < ARRAYSIZE(Cube); i++)
    {
        Rotation(0.05f, input, 0.05f, Cube[i].ScreenCoordinates);
    }
////////////////////////////////////////////////////////////

    D3D11_MAPPED_SUBRESOURCE map;
    gp_DeviceContext->Map(gp_VBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &map);
    memcpy(map.pData, Cube, sizeof(Cube));
    gp_DeviceContext->Unmap(gp_VBuffer, NULL);

    UINT stride = sizeof(Vertex);
    UINT offset = 0;
    gp_DeviceContext->IASetIndexBuffer(gp_IBuffer, DXGI_FORMAT_R16_UINT, 0);
    gp_DeviceContext->RSSetState(gp_rs);
    gp_DeviceContext->IASetVertexBuffers(0, 1, &gp_VBuffer, &stride, &offset);
    gp_DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    gp_DeviceContext->DrawIndexed(ARRAYSIZE(CubeIndices), 0, 0);
    gp_DXGI_SwapChain->Present(0, 0);
}
//-------------------DỌN DẸP CỬA SỔ -------------//
void CleanD3D()
{
    SafeRelease(gp_Device);
    SafeRelease(gp_DeviceContext);

    SafeRelease(gp_DXGI_Device);
    SafeRelease(gp_DXGI_Adapter);
    SafeRelease(gp_DXGI_Factory);
    SafeRelease(gp_DXGI_SwapChain);

    SafeRelease(gp_VS);
    SafeRelease(gp_PS);
    SafeRelease(gp_InputLayout);
    SafeRelease(gp_IBuffer);
    SafeRelease(gp_rs);

    SafeRelease(gp_BackBuffer);
}

LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        Render();
        ValidateRect(hWnd, NULL);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
        break;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

WindowManager.h

#pragma once
#include <Windows.h>

struct WindowManager
{
    static void InitWindowExW(UINT style, WNDPROC lpfnWndProc, int cbClsExtra, int cbWndExtra, HINSTANCE hInstance, LPCWSTR lpszClassName, LPCWSTR lpszMenuName, LPCWSTR hCursor, LPCWSTR hIcon, LPCWSTR hIconSm, HBRUSH hbrBackground) {
        WNDCLASSEXW wndClass = {};
        wndClass.cbSize = sizeof(WNDCLASSEXW);
        wndClass.style = style;
        wndClass.lpfnWndProc = lpfnWndProc;
        wndClass.cbClsExtra = cbClsExtra;
        wndClass.hInstance = hInstance;
        wndClass.lpszClassName = lpszClassName;
        wndClass.lpszMenuName = lpszMenuName;
        wndClass.hCursor = LoadCursorW(hInstance, hCursor);
        wndClass.hIcon = LoadIconW(hInstance, hIcon);
        wndClass.hIconSm = LoadIconW(hInstance, hIconSm);
        wndClass.hbrBackground = hbrBackground;

        if (!RegisterClassExW(&wndClass)) {
            
        }
    }
};

EngineGraphics.h

#pragma once
#include <d3d11.h>
#include <dxgi1_2.h>
#include <d3dcompiler.h>
#include "ExtroEngine.h"
#include "EngineUtility.h"

#define SCENE_HEIGHT 600
#define SCENE_HEIGHT_MAX_HD 3300
#define SCENE_WIDTH_MAX_HD 2240
#define SCENE_WIDTH 600

using namespace DirectX;

struct Vertex
{
    XMFLOAT3 ScreenCoordinates;
    XMFLOAT4 Color;
};

template<typename T>
inline void SafeRelease(T& ptr)
{
    if (ptr != NULL)
    {
        ptr->Release();
        ptr = NULL;
    }
}

ExtroEngine.cpp

#include "ExtroEngine.h"

namespace ExtroEngine
{
    float Input::GetKeyRaw(Key key)
    {
        MSG msg = { 0 };
        if (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            switch (msg.message)
            {
            case WM_KEYDOWN:
                if (msg.wParam == key)
                {
                    return 1.0f;
                }
                break;
            }
        }
        return 0.0f;
    }

    void Rotation(float AngleX, float AngleY, float AngleZ, XMFLOAT3& point)
    {
        float rX = AngleX * (XM_PI / 180.0f);
        float rY = AngleY * (XM_PI / 180.0f);
        float rZ = AngleZ * (XM_PI / 180.0f);

        XMMATRIX rotationMatrixX = XMMatrixRotationX(rX);
        XMMATRIX rotationMatrixY = XMMatrixRotationY(rY);
        XMMATRIX rotationMatrixZ = XMMatrixRotationZ(rZ);

        XMMATRIX finalRotationMatrix = rotationMatrixX * rotationMatrixY * rotationMatrixZ;

        XMVECTOR pointVector = XMLoadFloat3(&point);
        pointVector = XMVector3TransformCoord(pointVector, finalRotationMatrix);
        XMStoreFloat3(&point, pointVector);
    }
    void Translation(float x, float y, float z, XMFLOAT3& point)
    {
        XMVECTOR pV = XMLoadFloat3(&point);
        XMMATRIX translationMatrix = XMMatrixTranslation(x, y, z);
        XMVECTOR transformedPoint = XMVector3TransformCoord(pV, translationMatrix);
        XMStoreFloat3(&point, transformedPoint);
    }

    float Time::DeltaTime = 0.0f;
}

ExtroEngine.h

#pragma once
#include "Key.h"
#include <DirectXMath.h>

using namespace DirectX;

namespace ExtroEngine
{
    class Transform
    {
    public:
        XMFLOAT3 Position;
        XMFLOAT3 Rotation;
        XMFLOAT3 Scale;
        Transform()
        {
            Position = { 0.0f,0.0f,0.0f };
            Rotation = { 0.0f,0.0f,0.0f };
            Scale = { 0.0f,0.0f,0.0f };
        }
    };
    struct Input
    {
        static float GetKeyRaw(Key key);
    };

    struct Time
    {
        static float DeltaTime;
    };

    void Rotation(float AngleX, float AngleY, float AngleZ, XMFLOAT3& point);
    void Translation(float x, float y, float z, XMFLOAT3& point);
}

and Key.h

#pragma once
#include <Windows.h>

typedef enum Key {
    A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F', G = 'G', H = 'H', I = 'I', J = 'J', K = 'K',
    L = 'L', M = 'M', N = 'N', O = 'O', P = 'P', Q = 'Q', R = 'R', S = 'S', T = 'T', U = 'U', V = 'V',
    W = 'W', X = 'X', Y = 'Y', Z = 'Z',
    NUM_0 = '0', NUM_1 = '1', NUM_2 = '2', NUM_3 = '3', NUM_4 = '4', NUM_5 = '5', NUM_6 = '6',
    NUM_7 = '7', NUM_8 = '8', NUM_9 = '9',
    ESC = VK_ESCAPE
} Key;

add ColorVPS.hlsl

struct VOut
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
    VOut output;

    output.position = position;
    output.color = color;

    return output;
}

float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
    return color;
}

void main(in float4 position : POSITION, in float4 color : COLOR, out float4 oPosition : SV_POSITION, out float4 oColor : COLOR)
{
    VOut output = VShader(position, color);
    
    oPosition = output.position;
    oColor = output.color;
}

Notes: I'm using DirectX 11 for rendering and handling input events.

The application runs without any errors or crashes, but the described issues with cube rotation and window close button persist.

I want to know where my mistake is in the code


Solution

  • There are multiple ways to fix this.

    First, you could use the official message loop from Using Messages and Message Queues, so replace your while(TRUE) ... by this:

    BOOL bRet;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
    {
        if (bRet == -1)
        {
            // handle the error and possibly exit
            break;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            // don't put Render() here
        }
    }
    

    And then do your render loop in WM_PAINT. This is what you wanted to do but one important detail is as doc says:

    Return value An application returns zero if it processes this message.

    So replace your WM_PAINT handling by this:

    case WM_PAINT:
      Render();
      return 0; // we handle it
    

    What it means to Windows is in fact you never validate your update region so Windows keeps sending the WM_PAINT message.

    Another solution (more common in DirectX gaming loops) is to keep Render in your current message loop and handle WM_PAINT just validating the region, like this, so Windows won't send it forever:

    case WM_PAINT:
    {
      PAINTSTRUCT ps;
      HDC hdc = BeginPaint(hWnd, &ps);
      EndPaint(hWnd, &ps);
    }
    return 0;
    

    And change your current Input::GetKeyRaw(Key key) implementation because it runs another message loop which causes weird things. Move WM_KEYDOWN handling in the main loop, and simply store the keyboard state somewhere so you can use it in Render().