Drawing missing in bitmap in a pure C win32 program

A

afurtado

Hi,

There is something preventing the drawing of a cube appear on the black window. Can you help me to find the bug?

C:
#define _GNU_SOURCE

#include <windows.h>
#include <windowsx.h>
#include <stdbool.h>
#include <math.h>

#define WIDTH 800
#define HEIGHT 600

#define BMAPX   300
#define BMAPY   100

#define TRACKBALLSIZE  (0.7f)

#define L    64

BITMAPINFO bmInfo;
HDC myCompatibleDC;
HBITMAP myBitmap;
HWND hwnd;
HPEN xPen;

// Trackball

boolean drag;
float lastQ[4];
float currQ[4];
int startx, starty;
float rotation[4];
float scale = 1;

void vzero(float *v)
{
    v[0] = 0.0;
    v[1] = 0.0;
    v[2] = 0.0;
}

void vset(float *v, float x, float y, float z)
{
    v[0] = x;
    v[1] = y;
    v[2] = z;
}

void vsub(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] - src2[0];
    dst[1] = src1[1] - src2[1];
    dst[2] = src1[2] - src2[2];
}

void vcopy(const float *v1, float *v2)
{
    register int i;
    for (i = 0 ; i < 3 ; i++)
        v2[i] = v1[i];
}

void vcross(const float *v1, const float *v2, float *cross)
{
    float temp[3];

    temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
    temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
    temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
    vcopy(temp, cross);
}

float vlength(const float *v)
{
    return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

void vscale(float *v, float div)
{
    v[0] *= div;
    v[1] *= div;
    v[2] *= div;
}

void vnormal(float *v)
{
    vscale(v,1.0/vlength(v));
}

float vdot(const float *v1, const float *v2)
{
    return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}

void vadd(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] + src2[0];
    dst[1] = src1[1] + src2[1];
    dst[2] = src1[2] + src2[2];
}

void axis_to_quat(float a[3], float phi, float q[4])
{
    vcopy(a,q);
    vnormal(q);
    vscale(q,sin(phi/2.0));
    q[3] = cos(phi/2.0);
}

void mul(float *r, float *a, float *b)
{
    float w1 = a[0], x1 = a[1], y1 = a[2], z1 = a[3];
    float w2 = b[0], x2 = b[1], y2 = b[2], z2 = b[3];

    r[0] = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;  // Scalar (real) part
    r[1] = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;  // i (x) component
    r[2] = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;  // j (y) component
    r[3] = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;  // k (z) component
}

/*
 * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
 * if we are away from the center of the sphere.
 */
float tb_project_to_sphere(float r, float x, float y)
{
    float d, t, z;

    d = sqrt(x * x + y * y);
    r = r * 0.7;
    if (d < r)
    {
        // Inside sphere
        z = sqrt(r * r - d * d);
    }
    else
    {
        // On hyperbola
        t = r / 1.41421356237309504880;
        z = (t * t) / d;
    }
    return z;
}

/*
 * Arguments to this routine are in the range (-1.0 ... 1.0).
 */
void trackball(float q[4], float p1x, float p1y, float p2x, float p2y)
{
    float a[3]; /* Axis of rotation */
    float phi;  /* how much to rotate about axis */
    float p1[3], p2[3], d[3];
    float t;
    if (p1x == p2x && p1y == p2y)
    {
        vzero(q);
        q[3] = L;
        return;
    }
    vset(p1,p1x,p1y,tb_project_to_sphere(TRACKBALLSIZE,p1x,p1y));
    vset(p2,p2x,p2y,tb_project_to_sphere(TRACKBALLSIZE,p2x,p2y));
    vcross(p2,p1,a);
    vsub(p1,p2,d);
    t = vlength(d) / (2.0*TRACKBALLSIZE);
    if (t > 1.0) t = L;
    if (t < -1.0) t = -L;
    phi = 2.0 * asin(t);
    axis_to_quat(a, phi, q);
}

void normalize_quat(float q[4])
{
    int i;
    float mag;

    mag = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
    for (i = 0; i < 4; i++) q[i] /= mag;
}

void add_quats(float q1[4], float q2[4], float dest[4])
{
    static int count=0;
    float t1[4], t2[4], t3[4];
    float tf[4];
    vcopy(q1,t1);
    vscale(t1,q2[3]);
    vcopy(q2,t2);
    vscale(t2,q1[3]);
    vcross(q2,q1,t3);
    vadd(t1,t2,tf);
    vadd(t3,tf,tf);
    tf[3] = q1[3] * q2[3] - vdot(q1,q2);
    dest[0] = tf[0];
    dest[1] = tf[1];
    dest[2] = tf[2];
    dest[3] = tf[3];
    if (++count > 97)
    {
        count = 0;
        normalize_quat(dest);
    }
}

void scaleQuat(float quat[4])
{
    int i;
    for (i = 0; i < 4; i++)
    {
        quat[i] *= scale;
    }
}

void rotateVector(float *rotated, float *rotation, float *object)
{
    // Quaternion multiplication: q_rotated = q_rotation * q_object * q_conjugate(rotation)
    float q_rotation[4] = { rotation[0], -rotation[1], -rotation[2], -rotation[3] };
    float q_object[4] = { 0.0f, object[0], object[1], object[2] };
    float q_conjugate[4] = { rotation[0], rotation[1], rotation[2], rotation[3] };

    float temp1[4];
    float temp2[4];

    // Multiply q_rotation and q_object
    mul(temp1, q_rotation, q_object);

    // Multiply the result by q_conjugate
    mul(temp2, temp1, q_conjugate);

    // Extract the vector part from the result quaternion
    rotated[0] = temp2[1];
    rotated[1] = temp2[2];
    rotated[2] = temp2[3];
}

void projLine(HDC hdc, float point1[3], float point2[3])
{
    float rotated[3];
    rotateVector(rotated, rotation, point1);

    MoveToEx(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2, NULL);
    rotateVector(rotated, rotation, point2);
    LineTo(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2);
}

void drawModel2(HDC hdc)
{
    SelectObject(hdc, xPen);
    // Draw a simple cube
    float p[8][3] = {
        {-L, -L, -L},
        {L, -L, -L},
        {L, L, -L},
        {-L, L, -L},
        {-L, -L, L},
        {L, -L, L},
        {L, L, L},
        {-L, L, L}
    };

    for (int i = 0; i < 4; i++)
        projLine(hdc, p[i], p[(i + 1) % 4]);

    for (int i = 0; i < 4; i++)
        projLine(hdc, p[i + 4], p[((i + 1) % 4) + 4]);

    for (int i = 0; i < 4; i++)
        projLine(hdc, p[i], p[i + 4]);
}


void drawModel(HDC hdc)
{
    SelectObject(hdc, xPen);
    // Draw a simple cube
    unsigned long shift, b[3] = { 0x00be6b3eUL, 0x0069635fUL, 0x0010b088UL };
    float p[2][3];
    p[0][0] = rand() % L - L/2;
    p[0][1] = rand() % L - L/2;
    p[0][2] = rand() % L - L/2;
    p[1][0] = rand() % L - L/2;
    p[1][1] = rand() % L - L/2;
    p[1][2] = rand() % L - L/2;

    projLine(hdc, p[0], p[1]);



    for (int i = 0; i < 72; i++, shift >>= 1)
    {
        if(i % 24 == 0) shift = b[i/24];
            p[(i / 3) % 2][i % 3] = (shift & 1) == 0 ? +L : -L;
        if((i + 1) % 6 == 0) projLine(hdc, p[0], p[1]);
      }
}

LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    int x = GET_X_LPARAM(lparam);
    int y = GET_Y_LPARAM(lparam);
    switch(msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            // GUI here

            // Draw the 3d bitmap
            HDC hdcMem = CreateCompatibleDC(hdc);
            SelectObject(hdcMem, myBitmap);
            RECT rect;
            rect.left = 0;
            rect.bottom = 0;
            rect.right = WIDTH;
            rect.top = HEIGHT;
            HBRUSH hbrBkGnd = CreateSolidBrush(RGB(0, 0, 0));
            FillRect(hdcMem, &rect, hbrBkGnd);
            DeleteObject(hbrBkGnd);
            SetBkMode(hdcMem, TRANSPARENT);
            drawModel(hdcMem);
            drawModel2(hdcMem);
            BitBlt(hdc, BMAPX, BMAPY, WIDTH, HEIGHT, hdcMem, 0, 0, SRCCOPY);
            DeleteDC(hdcMem);
            EndPaint(hwnd, &ps);
            break;
        }
        case WM_CREATE:
        {
            ZeroMemory(&bmInfo, sizeof(BITMAPINFO));
            bmInfo.bmiHeader.biBitCount    = 32;
            bmInfo.bmiHeader.biHeight      = HEIGHT;
            bmInfo.bmiHeader.biPlanes      = 1;
            bmInfo.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
            bmInfo.bmiHeader.biWidth       = WIDTH;
            bmInfo.bmiHeader.biCompression = BI_RGB;
            // Initial orientation
            lastQ[0] = 1;
            lastQ[1] = 0;
            lastQ[2] = 0;
            lastQ[3] = 0;
            // Identity
            currQ[0] = 1;
            currQ[1] = 0;
            currQ[2] = 0;
            currQ[3] = 0;
            drag = false;
            xPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
            // Start the rendering loop
            SetTimer(hwnd, 1, 32, NULL); // 32 ms interval for approximately 60 FPS
            break;
        }
        case WM_ERASEBKGND: // avoid flicker
            return 1;

        case WM_DESTROY:
            ReleaseCapture();
            DeleteObject(myBitmap) ;
            DeleteDC(myCompatibleDC);
            PostQuitMessage(0);
            exit(0);
            break;
        case WM_LBUTTONDOWN:
            startx = x;
            starty = y;
            drag = true;
            SetCapture(hwnd);
            return 0;

        case WM_LBUTTONUP:
            if (!drag)
                break;
            mul(lastQ, currQ, lastQ);
            currQ[0] = 1;
            currQ[1] = 0;
            currQ[2] = 0;
            currQ[3] = 0;
            drag = false;
            ReleaseCapture();
            break;

        case WM_MOUSEMOVE:
        {
            if (!drag)
                break;
            float dquat[4];
            trackball (dquat,
                 (2.0*startx - WIDTH) / WIDTH,
                 (HEIGHT - 2.0*starty) / HEIGHT,
                 (2.0*x - WIDTH) / WIDTH,
                 (HEIGHT - 2.0*y) / HEIGHT);
            add_quats (lastQ, dquat, currQ);
            break;
        }
        case WM_TIMER:
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return lparam;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    CreateEvent(NULL, FALSE, FALSE, "Launching...");
    WNDCLASS wc;
    MSG msg;
    ZeroMemory(&wc, sizeof(WNDCLASS));
    //
    wc.hInstance     = hInstance;
    wc.lpfnWndProc   = MyWndProc;
    wc.lpszClassName = "MYWNDCLASSNAME";
    wc.hbrBackground = NULL;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    //
     // Get the dimensions of the screen
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    RegisterClass(&wc);
    hwnd = CreateWindow("MYWNDCLASSNAME", "C Forvm",
        WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        20, 20, screenWidth - 500, screenHeight - 100, NULL, NULL, hInstance, NULL);

    // Show window

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    // Create the bitmap child window

    HDC hdc = GetDC(hwnd);
    myBitmap = CreateCompatibleBitmap(hdc, WIDTH, HEIGHT);
    HWND g_hBitmap = CreateWindow("STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_BITMAP, BMAPX, BMAPY, 0, 0, hwnd, NULL, hInstance, NULL);
    SendMessage(g_hBitmap, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)myBitmap);
    ReleaseDC(hwnd, hdc);
    InvalidateRect(hwnd, NULL, TRUE);
    while(GetMessage(&msg, NULL, 0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
 
Joined
Mar 31, 2023
Messages
95
Reaction score
8
I noticed a few issues in the code that could be causing the cube not to appear or other problems:

Windows API related issues:
  • The WM_CREATE case in the message loop is missing a break statement. Without the break, the code will fall through to the WM_ERASEBKGND case, which is not intended.
  • In the WM_DESTROY case, you are calling exit(0) instead of return 0 or PostQuitMessage(0) to terminate the application. It is recommended to use PostQuitMessage(0) to properly clean up resources and notify the system to exit the message loop.
  • You are missing a break statement after SetCapture(hwnd) in the WM_LBUTTONDOWN case. This issue may not be causing any immediate problems, but it's good practice to include a break statement to prevent unintentional fall-through behavior.
3D rendering issues:
  • The code defines two different drawModel functions: drawModel and drawModel2. It seems that you intended to draw the cube using the drawModel2 function, but the drawModel function is still being called in the WM_PAINT case. You should modify the WM_PAINT case to call drawModel2 instead of drawModel.
  • In the drawModel2 function, you are using the projLine function to draw lines between different points of the cube. However, the projLine function is not correctly implemented. It should handle the projection and rotation of the points before drawing the lines. You need to fix the implementation of the projLine function to correctly rotate the points before drawing the lines.
I suggest addressing these issues and testing the application again to see if the cube appears correctly.
 
A

afurtado

Thank you for the kind reply. The problem was indeed in the lack of projection and rotation before drawing the lines, as you said. I added mul(rotation, currQ, lastQ); in line 218 and the cube appeared.
Now I face another issue, the dragging of the cube is somewhat irregular. I spent a lot of time, but could not figure out what is going on. The update version follows:


C:
#define _GNU_SOURCE

#include <windows.h>
#include <windowsx.h>
#include <stdbool.h>
#include <math.h>

#define WIDTH 800
#define HEIGHT 600

#define BMAPX   300
#define BMAPY   100

#define TRACKBALLSIZE  (0.7f)

#define L    256

BITMAPINFO bmInfo;
HDC myCompatibleDC;
HBITMAP myBitmap;
HWND hwnd;
HPEN xPen;

// Trackball

boolean drag;
float lastQ[4];
float currQ[4];
int startx, starty;
float rotation[4];

void vzero(float *v)
{
    v[0] = 0.0;
    v[1] = 0.0;
    v[2] = 0.0;
}

void vset(float *v, float x, float y, float z)
{
    v[0] = x;
    v[1] = y;
    v[2] = z;
}

void vsub(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] - src2[0];
    dst[1] = src1[1] - src2[1];
    dst[2] = src1[2] - src2[2];
}

void vcopy(const float *v1, float *v2)
{
    register int i;
    for (i = 0 ; i < 3 ; i++)
        v2[i] = v1[i];
}

void vcross(const float *v1, const float *v2, float *cross)
{
    float temp[3];

    temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
    temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
    temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
    vcopy(temp, cross);
}

float vlength(const float *v)
{
    return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

void vscale(float *v, float div)
{
    v[0] *= div;
    v[1] *= div;
    v[2] *= div;
}

void vnormal(float *v)
{
    vscale(v, 1.0/vlength(v));
}

float vdot(const float *v1, const float *v2)
{
    return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}

void vadd(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] + src2[0];
    dst[1] = src1[1] + src2[1];
    dst[2] = src1[2] + src2[2];
}

void axis_to_quat(float a[3], float phi, float q[4])
{
    vcopy(a, q + 1);
    vnormal(q + 1);
    vscale(q + 1, sin(phi/2.0));
    q[0] = cos(phi / 2.0);
}

/*
 * q[0] corresponds to w.
 */
void mul(float *r, float *a, float *b)
{
    float w1 = a[0], x1 = a[1], y1 = a[2], z1 = a[3];
    float w2 = b[0], x2 = b[1], y2 = b[2], z2 = b[3];

    r[0] = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;  // Scalar (real) part
    r[1] = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;  // i (x) component
    r[2] = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;  // j (y) component
    r[3] = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;  // k (z) component
}

/*
 * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
 * if we are away from the center of the sphere.
 */
float tb_project_to_sphere(float r, float x, float y)
{
    float d, t, z;

    d = sqrt(x * x + y * y);
    r = r * 0.7;
    if (d < r)
    {
        // Inside sphere
        z = sqrt(r * r - d * d);
    }
    else
    {
        // On hyperbola
        t = r / 1.41421356237309504880;
        z = (t * t) / d;
    }
    return z;
}

void trackball(float q[4], float p1x, float p1y, float p2x, float p2y)
{
    float a[3]; /* Axis of rotation */
    float phi;  /* how much to rotate about axis */
    float p1[3], p2[3], d[3];
    float t;
    if (p1x == p2x && p1y == p2y)
    {
        vzero(q);
        q[0] = L;
        return;
    }
    vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y));
    vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y));
    vcross(p2, p1, a);
    vsub(p1, p2, d);
    t = vlength(d) / (2.0 * TRACKBALLSIZE);
    if (t > 1.0) t = L;
    if (t < -1.0) t = -L;
    phi = 2.0 * asin(t);
    axis_to_quat(a, phi, q);
}

void normalize_quat(float q[4])
{
    int i;
    float mag;

    mag = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
    for (i = 0; i < 4; i++) q[i] /= mag;
}

void add_quats(float q1[4], float q2[4], float dest[4])
{
    for (register int i = 0; i < 4; i++) dest[i] = q1[i] + q2[i];
}

/*
 * q[0] corresponds to w.
 */
void rotateVector(float *rotated, float *rotation, float *object)
{
    // Quaternion multiplication: q_rotated = q_rotation * q_object * q_conjugate(rotation)
    float q_rotation[4] = { rotation[0], -rotation[1], -rotation[2], -rotation[3] };
    float q_object[4] = { 0.0f, object[0], object[1], object[2] };
    float q_conjugate[4] = { rotation[0], rotation[1], rotation[2], rotation[3] };

    float temp1[4];
    float temp2[4];

    // Multiply q_rotation and q_object
    mul(temp1, q_rotation, q_object);

    // Multiply the result by q_conjugate
    mul(temp2, temp1, q_conjugate);

    // Extract the vector part from the result quaternion
    rotated[0] = temp2[1];
    rotated[1] = temp2[2];
    rotated[2] = temp2[3];
}

void projLine(HDC hdc, float point1[3], float point2[3])
{
    float rotated[3];
    rotateVector(rotated, rotation, point1);
    MoveToEx(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2, NULL);
    rotateVector(rotated, rotation, point2);
    LineTo(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2);
}

void drawModel(HDC hdc)
{
    mul(rotation, currQ, lastQ);    // patch
    SelectObject(hdc, xPen);
    // Draw a simple cube
    unsigned long shift, b[3] = { 0x00be6b3eUL, 0x0069635fUL, 0x0010b088UL };
    float p[2][3];
    for (int i = 0; i < 72; i++, shift >>= 1)
    {
        if(i % 24 == 0) shift = b[i/24];
        p[(i / 3) % 2][i % 3] = (shift & 1) == 0 ? +L : -L;
        if((i + 1) % 6 == 0) projLine(hdc, p[0], p[1]);
      }
}

LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    int x = GET_X_LPARAM(lparam);
    int y = GET_Y_LPARAM(lparam);
    switch(msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            // GUI here

            // Draw the 3d bitmap
            HDC hdcMem = CreateCompatibleDC(hdc);
            SelectObject(hdcMem, myBitmap);
            RECT rect;
            rect.left = 0;
            rect.bottom = 0;
            rect.right = WIDTH;
            rect.top = HEIGHT;
            HBRUSH hbrBkGnd = CreateSolidBrush(RGB(0, 0, 0));
            FillRect(hdcMem, &rect, hbrBkGnd);
            DeleteObject(hbrBkGnd);
            SetBkMode(hdcMem, TRANSPARENT);
            drawModel(hdcMem);
            BitBlt(hdc, BMAPX, BMAPY, WIDTH, HEIGHT, hdcMem, 0, 0, SRCCOPY);
            DeleteDC(hdcMem);
            EndPaint(hwnd, &ps);
            break;
        }
        case WM_CREATE:
        {
            ZeroMemory(&bmInfo, sizeof(BITMAPINFO));
            bmInfo.bmiHeader.biBitCount    = 32;
            bmInfo.bmiHeader.biHeight      = HEIGHT;
            bmInfo.bmiHeader.biPlanes      = 1;
            bmInfo.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
            bmInfo.bmiHeader.biWidth       = WIDTH;
            bmInfo.bmiHeader.biCompression = BI_RGB;
            // Initial orientation
            lastQ[0] = 1;
            lastQ[1] = 0;
            lastQ[2] = 0;
            lastQ[3] = 0;
            // Identity
            currQ[0] = 1;
            currQ[1] = 0;
            currQ[2] = 0;
            currQ[3] = 0;
            drag = false;
            xPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
            // Start the rendering loop
            SetTimer(hwnd, 1, 32, NULL); // 32 ms interval for approximately 60 FPS
            break;
        }
        case WM_ERASEBKGND: // avoid flicker
            return 1;

        case WM_DESTROY:
            ReleaseCapture();
            DeleteObject(myBitmap) ;
            DeleteDC(myCompatibleDC);
            PostQuitMessage(0);
            exit(0);
            break;
        case WM_LBUTTONDOWN:
            startx = x;
            starty = y;
            drag = true;
            SetCapture(hwnd);
            break;

        case WM_LBUTTONUP:
            if (!drag)
                break;
            mul(lastQ, currQ, lastQ);
            currQ[0] = 1;
            currQ[1] = 0;
            currQ[2] = 0;
            currQ[3] = 0;
            drag = false;
            ReleaseCapture();
            break;

        case WM_MOUSEMOVE:
        {
            if (!drag)
                break;
            float dquat[4];
            trackball (dquat,
                 (2.0*startx - WIDTH) / WIDTH,
                 (HEIGHT - 2.0*starty) / HEIGHT,
                 (2.0*x - WIDTH) / WIDTH,
                 (HEIGHT - 2.0*y) / HEIGHT);
            add_quats (lastQ, dquat, currQ);
            normalize_quat(currQ); // patch
            break;
        }
        case WM_TIMER:
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return lparam;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    CreateEvent(NULL, FALSE, FALSE, "Launching...");
    WNDCLASS wc;
    MSG msg;
    ZeroMemory(&wc, sizeof(WNDCLASS));
    //
    wc.hInstance     = hInstance;
    wc.lpfnWndProc   = MyWndProc;
    wc.lpszClassName = "MYWNDCLASSNAME";
    wc.hbrBackground = NULL;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    //
     // Get the dimensions of the screen
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    RegisterClass(&wc);
    hwnd = CreateWindow("MYWNDCLASSNAME", "C Forvm",
        WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        20, 20, screenWidth - 500, screenHeight - 100, NULL, NULL, hInstance, NULL);

    // Show window

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    // Create the bitmap child window

    HDC hdc = GetDC(hwnd);
    myBitmap = CreateCompatibleBitmap(hdc, WIDTH, HEIGHT);
    HWND g_hBitmap = CreateWindow("STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_BITMAP, BMAPX, BMAPY, 0, 0, hwnd, NULL, hInstance, NULL);
    SendMessage(g_hBitmap, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)myBitmap);
    ReleaseDC(hwnd, hdc);
    InvalidateRect(hwnd, NULL, TRUE);
    while(GetMessage(&msg, NULL, 0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
 
Joined
Mar 31, 2023
Messages
95
Reaction score
8
It seems that the irregular dragging of the cube may be caused by a small error in the code. In the MyWndProc function, specifically in the WM_MOUSEMOVE case, there is an error in the line return lparam;. It should be return 0; instead. This error is preventing the trackball rotation calculations from being executed correctly.

Here's the corrected code for the WM_MOUSEMOVE case:
Python:
case WM_MOUSEMOVE:
{
    if (!drag)
        break;
    float dquat[4];
    trackball (dquat,
         (2.0*startx - WIDTH) / WIDTH,
         (HEIGHT - 2.0*starty) / HEIGHT,
         (2.0*x - WIDTH) / WIDTH,
         (HEIGHT - 2.0*y) / HEIGHT);
    add_quats (lastQ, dquat, currQ);
    normalize_quat(currQ); // patch
    break;
}
Please update this part of the code in your program and see if it resolves the irregular dragging issue.
 
A

afurtado

Hi,

Thank you for you reply. Unfortunately it didn't resolved the issue.

I used chatgpt to watch my code and I arrived to a solution, although not perfect, is enough for my purpose.
The main issue was that successive click and drag operations were not concatenated, there was an irregular jump when dragging started. The code shown is the best I achieved. If, by the way, anyone makes it perfect, please let me know.

C:
#define _GNU_SOURCE

#include <windows.h>
#include <windowsx.h>
#include <stdbool.h>
#include <math.h>

#define WIDTH 800
#define HEIGHT 600

#define BMAPX   300
#define BMAPY   100

#define TRACKBALLSIZE  0.5

#define L    256

BITMAPINFO bmInfo;
HDC myCompatibleDC;
HBITMAP myBitmap;
HWND hwnd;
HPEN xPen;

// Trackball

boolean drag;
float lastQ[4], currQ[4];
int startx, starty;
float rotation[4];

void vzero(float *v)
{
    v[0] = 0.0;
    v[1] = 0.0;
    v[2] = 0.0;
}

void vset(float *v, float x, float y, float z)
{
    v[0] = x;
    v[1] = y;
    v[2] = z;
}

void vsub(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] - src2[0];
    dst[1] = src1[1] - src2[1];
    dst[2] = src1[2] - src2[2];
}

void vcopy(const float *v1, float *v2)
{
    register int i;
    for (i = 0 ; i < 3 ; i++)
        v2[i] = v1[i];
}

void vcross(const float *v1, const float *v2, float *cross)
{
    float temp[3];

    temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
    temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
    temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
    vcopy(temp, cross);
}

float vlength(const float *v)
{
    return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

void vscale(float *v, float div)
{
    v[0] *= div;
    v[1] *= div;
    v[2] *= div;
}

void vnormal(float *v)
{
    vscale(v, 1.0/vlength(v));
}

float vdot(const float *v1, const float *v2)
{
    return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}

void vadd(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] + src2[0];
    dst[1] = src1[1] + src2[1];
    dst[2] = src1[2] + src2[2];
}

void axis_to_quat(float a[3], float phi, float q[4])
{
    vcopy(a, q + 1);
    vnormal(q + 1);
    vscale(q + 1, sin(phi / 2.0));
    q[0] = cos(phi/2.0);
}

void mul(float* r, float* a, float* b)
{
    r[0] = a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3];  // w component
    r[1] = a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2];  // x component
    r[2] = a[0] * b[2] - a[1] * b[3] + a[2] * b[0] + a[3] * b[1];  // y component
    r[3] = a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0];  // z component
}

/*
 * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
 * if we are away from the center of the sphere.
 */
float tb_project_to_sphere(float r, float x, float y)
{
    float d, t, z;

    d = sqrt(x * x + y * y);
    r = r * 0.7;
    if (d < r)
    {
        // Inside sphere
        z = sqrt(r * r - d * d);
    }
    else
    {
        // On hyperbola
        t = r / 1.41421356237309504880;
        z = (t * t) / d;
    }
    return z;
}

void trackball(float q[4], float p1x, float p1y, float p2x, float p2y)
{
    float a[3]; /* Axis of rotation */
    float phi;  /* how much to rotate about axis */
    float p1[3], p2[3], d[3];
    float t;
    if (p1x == p2x && p1y == p2y)
    {
        vzero(q);
        q[0] = L;
        return;
    }
    vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y));
    vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y));
    vcross(p2, p1, a);
    vsub(p1, p2, d);
    t = vlength(d) / (2.0 * TRACKBALLSIZE);
    if (t > 1.0) t = L;
    if (t < -1.0) t = -L;
    phi = 2.0 * asin(t);
    axis_to_quat(a, phi, q);
}

void normalize_quat(float q[4])
{
    float mag = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
    for (register int i = 0; i < 4; i++) q[i] /= mag;
}

void add_quats(float q1[4], float q2[4], float dest[4])
{
    for (register int i = 0; i < 4; i++) dest[i] = q1[i] + q2[i];
}

/*
 * q[0] corresponds to w.
 */
void rotateVector(float *rotated, float *rotation, float *object)
{
    float q_object[4] = { 0, object[0], object[1], object[2] };
    float q_conjugate[4] = { rotation[0], -rotation[1], -rotation[2], -rotation[3] };
    float temp1[4], temp2[4];
    mul(temp1, rotation, q_object);
    mul(temp2, temp1, q_conjugate);
    rotated[0] = temp2[1];
    rotated[1] = temp2[2];
    rotated[2] = temp2[3];
}

void projLine(HDC hdc, float point1[3], float point2[3])
{
    float rotated[3];
    rotateVector(rotated, rotation, point1);
    MoveToEx(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2, NULL);
    rotateVector(rotated, rotation, point2);
    LineTo(hdc, WIDTH/2 + rotated[0]/2, HEIGHT/2 + rotated[1]/2);
}

void drawModel(HDC hdc)
{
    mul(rotation, currQ, lastQ);
    SelectObject(hdc, xPen);
    // Draw a simple cube
    unsigned long shift, b[3] = { 0x00be6b3eUL, 0x0069635fUL, 0x0010b088UL };
    float p[2][3];
    for (int i = 0; i < 72; i++, shift >>= 1)
    {
        if(i % 24 == 0) shift = b[i/24];
        p[(i / 3) % 2][i % 3] = (shift & 1) == 0 ? +L : -L;
        if((i + 1) % 6 == 0) projLine(hdc, p[0], p[1]);
      }
}

LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    int x = GET_X_LPARAM(lparam);
    int y = GET_Y_LPARAM(lparam);
    switch(msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            // GUI here

            // Draw the 3d bitmap
            HDC hdcMem = CreateCompatibleDC(hdc);
            SelectObject(hdcMem, myBitmap);
            RECT rect;
            rect.left = 0;
            rect.bottom = 0;
            rect.right = WIDTH;
            rect.top = HEIGHT;
            HBRUSH hbrBkGnd = CreateSolidBrush(RGB(0, 0, 0));
            FillRect(hdcMem, &rect, hbrBkGnd);
            DeleteObject(hbrBkGnd);
            SetBkMode(hdcMem, TRANSPARENT);
            drawModel(hdcMem);
            BitBlt(hdc, BMAPX, BMAPY, WIDTH, HEIGHT, hdcMem, 0, 0, SRCCOPY);
            DeleteDC(hdcMem);
            EndPaint(hwnd, &ps);
            break;
        }
        case WM_CREATE:
        {
            ZeroMemory(&bmInfo, sizeof(BITMAPINFO));
            bmInfo.bmiHeader.biBitCount    = 32;
            bmInfo.bmiHeader.biHeight      = HEIGHT;
            bmInfo.bmiHeader.biPlanes      = 1;
            bmInfo.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
            bmInfo.bmiHeader.biWidth       = WIDTH;
            bmInfo.bmiHeader.biCompression = BI_RGB;
            // Initial orientation
            lastQ[0] = 1;
            lastQ[1] = 0;
            lastQ[2] = 0.707;
            lastQ[3] = 0.707;
            normalize_quat(lastQ);
            // Identity
            currQ[0] = 1;
            currQ[1] = 0;
            currQ[2] = 0;
            currQ[3] = 0;
            drag = false;
            xPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
            // Start the rendering loop
            SetTimer(hwnd, 1, 32, NULL); // 32 ms interval for approximately 15 FPS
            break;
        }
        case WM_ERASEBKGND: // avoid flicker
            return 1;

        case WM_DESTROY:
            ReleaseCapture();
            DeleteObject(myBitmap) ;
            DeleteDC(myCompatibleDC);
            PostQuitMessage(0);
            exit(0);
            break;

        case WM_LBUTTONDOWN:
            startx = x;
            starty = y;
            drag = true;
            SetCapture(hwnd);
            break;

        case WM_LBUTTONUP:
            if (drag)
            {
                mul(lastQ, currQ, lastQ);
                currQ[0] = 1;
                currQ[1] = 0;
                currQ[2] = 0;
                currQ[3] = 0;
                drag = false;
                ReleaseCapture();
            }
            break;

        case WM_MOUSEMOVE:
        {
            if (drag)
            {
                trackball(currQ, (2.0*startx - WIDTH) / WIDTH,
                         (HEIGHT - 2.0*starty) / HEIGHT,
                         (2.0*x - WIDTH) / WIDTH,
                         (HEIGHT - 2.0*y) / HEIGHT);
                float tempQuat[4];
                mul(tempQuat, currQ, lastQ);
                normalize_quat(tempQuat);
                vcopy(tempQuat, lastQ);
                startx = x;
                starty = y;
            }
            break;
        }

        case WM_TIMER:
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    CreateEvent(NULL, FALSE, FALSE, "Launching...");
    WNDCLASS wc;
    MSG msg;
    ZeroMemory(&wc, sizeof(WNDCLASS));
    //
    wc.hInstance     = hInstance;
    wc.lpfnWndProc   = MyWndProc;
    wc.lpszClassName = "MYWNDCLASSNAME";
    wc.hbrBackground = NULL;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    //
     // Get the dimensions of the screen
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    RegisterClass(&wc);
    hwnd = CreateWindow("MYWNDCLASSNAME", "C Forvm",
        WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
        20, 20, screenWidth - 500, screenHeight - 100, NULL, NULL, hInstance, NULL);

    // Show window

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    // Create the bitmap child window

    HDC hdc = GetDC(hwnd);
    myBitmap = CreateCompatibleBitmap(hdc, WIDTH, HEIGHT);
    HWND g_hBitmap = CreateWindow("STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_BITMAP, BMAPX, BMAPY, 0, 0, hwnd, NULL, hInstance, NULL);
    SendMessage(g_hBitmap, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)myBitmap);
    ReleaseDC(hwnd, hdc);
    InvalidateRect(hwnd, NULL, TRUE);
    while(GetMessage(&msg, NULL, 0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,790
Messages
2,569,637
Members
45,346
Latest member
EstebanCoa

Latest Threads

Top