I’m trying to create panning capabilities for a Direct2D render target. The drawings on the render target should follow the mouse key while the mouse middle button is held down. The problem that is occurring is that the drawing is drifting away from the mouse the further you go.
I’ve tried calculating the translation I’m rendering on the render target in multiple different ways. First, I used GET_X_LPARAM(lParam);
to calculate and that is where I had the problem occur. I then found another method online using GetCursorPos();
and ScreenToClient();
but that resulted in the translation being completely off. I’ve also tried changing sizing on the actual render target in the hopes that that could be the reason but this did not help either. I’m included a simplified version of my header and source file. I could not simplify it more than this because I haven’t been able to pin down where the issue stems from sufficiently enough.
Header:
#pragma once
// Windows Header Files:
#include <windows.h>
// C RunTime Header Files:
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
template<class Interface>
inline void SafeRelease(
Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
#ifndef Assert
#if defined( DEBUG ) || defined( _DEBUG )
#define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "n");}} while(0)
#else
#define Assert(b)
#endif //DEBUG || _DEBUG
#endif
#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif
class DemoApp
{
public:
DemoApp();
~DemoApp();
// Register the window class and call methods for instantiating drawing resources
HRESULT Initialize();
// Process and dispatch messages
void RunMessageLoop();
private:
// Initialize device-independent resources.
HRESULT CreateDeviceIndependentResources();
// Initialize device-dependent resources.
HRESULT CreateDeviceResources();
// Release device-dependent resource.
void DiscardDeviceResources();
// Update transform.
void UpdateTransform();
// Draw content.
HRESULT OnRender();
// Resize the render target.
void OnResize(
UINT width,
UINT height
);
// The windows procedure.
static LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
private:
HWND m_hwnd;
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
static POINT pan_offset;
};
Source file:
#pragma comment(lib, "d2d1.lib")
#include "Header.h"
#include <iostream>
#include <windowsx.h>
#include <cmath>
POINT DemoApp::pan_offset;
DemoApp::DemoApp() :
m_hwnd(NULL),
m_pDirect2dFactory(NULL),
m_pRenderTarget(NULL),
m_pLightSlateGrayBrush(NULL),
m_pCornflowerBlueBrush(NULL)
{}
DemoApp::~DemoApp()
{
SafeRelease(&m_pDirect2dFactory);
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pLightSlateGrayBrush);
SafeRelease(&m_pCornflowerBlueBrush);
}
void DemoApp::RunMessageLoop()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
HRESULT DemoApp::Initialize()
{
HRESULT hr;
// Initialize device-independent resources, such
// as the Direct2D factory.
hr = CreateDeviceIndependentResources();
if (SUCCEEDED(hr))
{
// Register the window class.
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DemoApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
wcex.lpszClassName = L"D2DDemoApp";
RegisterClassEx(&wcex);
// In terms of using the correct DPI, to create a window at a specific size
// like this, the procedure is to first create the window hidden. Then we get
// the actual DPI from the HWND (which will be assigned by whichever monitor
// the window is created on). Then we use SetWindowPos to resize it to the
// correct DPI-scaled size, then we use ShowWindow to show it.
m_hwnd = CreateWindow(
L"D2DDemoApp",
L"Direct2D demo application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
NULL,
NULL,
HINST_THISCOMPONENT,
this);
if (m_hwnd)
{
// Because the SetWindowPos function takes its size in pixels, we
// obtain the window's DPI, and use it to scale the window size.
float dpi = GetDpiForWindow(m_hwnd);
SetWindowPos(
m_hwnd,
NULL,
NULL,
NULL,
static_cast<int>(ceil(640.f * dpi / 96.f)),
static_cast<int>(ceil(480.f * dpi / 96.f)),
SWP_NOMOVE);
ShowWindow(m_hwnd, SW_SHOWNORMAL);
UpdateWindow(m_hwnd);
}
}
return hr;
}
int WINAPI WinMain(
HINSTANCE /* hInstance */,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */
)
{
// Use HeapSetInformation to specify that the process should
// terminate if the heap manager detects an error in any heap used
// by the process.
// The return value is ignored, because we want to continue running in the
// unlikely event that HeapSetInformation fails.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (SUCCEEDED(CoInitialize(NULL)))
{
{
DemoApp app;
if (SUCCEEDED(app.Initialize()))
{
app.RunMessageLoop();
}
}
CoUninitialize();
}
return 0;
}
HRESULT DemoApp::CreateDeviceIndependentResources()
{
HRESULT hr = S_OK;
// Create a Direct2D factory.
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
return hr;
}
HRESULT DemoApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
if (!m_pRenderTarget)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right,
rc.bottom
);
// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
auto renderTargetSize = m_pRenderTarget->GetSize();
if (SUCCEEDED(hr))
{
// Create a gray brush.
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::LightSlateGray),
&m_pLightSlateGrayBrush
);
}
if (SUCCEEDED(hr))
{
// Create a blue brush.
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
&m_pCornflowerBlueBrush
);
}
}
return hr;
}
void DemoApp::DiscardDeviceResources()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pLightSlateGrayBrush);
SafeRelease(&m_pCornflowerBlueBrush);
}
HRESULT DemoApp::OnRender()
{
HRESULT hr = S_OK;
hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
m_pRenderTarget->BeginDraw();
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
// Draw a grid background.
int width = static_cast<int>(rtSize.width);
int height = static_cast<int>(rtSize.height);
// Draw two rectangles.
D2D1_RECT_F rectangle1 = D2D1::RectF(
rtSize.width / 2 - 50.0f,
rtSize.height / 2 - 50.0f,
rtSize.width / 2 + 50.0f,
rtSize.height / 2 + 50.0f
);
D2D1_RECT_F rectangle2 = D2D1::RectF(
rtSize.width / 2 - 100.0f,
rtSize.height / 2 - 100.0f,
rtSize.width / 2 + 100.0f,
rtSize.height / 2 + 100.0f
);
// Draw a filled rectangle.
m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
// Draw the outline of a rectangle.
m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
hr = m_pRenderTarget->EndDraw();
}
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
DiscardDeviceResources();
}
return hr;
}
void DemoApp::OnResize(UINT width, UINT height)
{
if (m_pRenderTarget)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
m_pRenderTarget->Resize(D2D1::SizeU(width, height));
}
}
void DemoApp::UpdateTransform()
{
D2D1_MATRIX_3X2_F transform =
D2D1::Matrix3x2F::Translation(pan_offset.x, pan_offset.y);
m_pRenderTarget->SetTransform(transform);
}
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
DemoApp* pDemoApp = (DemoApp*)pcs->lpCreateParams;
::SetWindowLongPtrW(
hwnd,
GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(pDemoApp)
);
result = 1;
}
else
{
DemoApp* pDemoApp = reinterpret_cast<DemoApp*>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(
hwnd,
GWLP_USERDATA
)));
bool wasHandled = false;
if (pDemoApp)
{
switch (message)
{
case WM_MOUSEMOVE:
{
static POINT lastMousePos;
if (wParam & MK_MBUTTON)
{
POINT currentMousePos{};
currentMousePos.x = GET_X_LPARAM(lParam);
currentMousePos.y = GET_Y_LPARAM(lParam);
pan_offset.x += static_cast<float>(currentMousePos.x - lastMousePos.x);
pan_offset.y += static_cast<float>(currentMousePos.y - lastMousePos.y);
pDemoApp->UpdateTransform();
lastMousePos = currentMousePos;
InvalidateRect(hwnd, NULL, FALSE); // Trigger redraw
}
else
{
lastMousePos.x = GET_X_LPARAM(lParam);
lastMousePos.y = GET_Y_LPARAM(lParam);
}
result = 0;
wasHandled = true;
break;
}
case WM_SIZE:
{
UINT width = LOWORD(lParam);
UINT height = HIWORD(lParam);
pDemoApp->OnResize(width, height);
}
result = 0;
wasHandled = true;
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect(hwnd, NULL, FALSE);
}
result = 0;
wasHandled = true;
break;
case WM_PAINT:
{
pDemoApp->OnRender();
ValidateRect(hwnd, NULL);
}
result = 0;
wasHandled = true;
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
result = 1;
wasHandled = true;
break;
}
}
if (!wasHandled)
{
result = DefWindowProc(hwnd, message, wParam, lParam);
}
}
return result;
}
user25472896 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.