Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!
  • Guest, before posting your code please take these rules into consideration:
    • It is required to use our BBCode feature to display your code. While within the editor click < / > or >_ and place your code within the BB Code prompt. This helps others with finding a solution by making it easier to read and easier to copy.
    • You can also use markdown to share your code. When using markdown your code will be automatically converted to BBCode. For help with markdown check out the markdown guide.
    • Don't share a wall of code. All we want is the problem area, the code related to your issue.


    To learn more about how to use our BBCode feature, please click here.

    Thank you, Code Forum.

Trackball code with glitch

Hi,

The code in pure win32 C below works fine, except that when I start a new dragging operation with the mouse, the model rotates by an certain amount. Can you take a look, please?

C:
        case WM_LBUTTONDOWN:
            startx = x;
            starty = y;
            drag = true;
            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;
            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;


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) {
        /* Zero rotation */
        vzero(q);
        q[3] = 1.0;
        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 = 1.0;
    if (t < -1.0) t = -1.0;
    phi = 2.0 * asin(t);
    axis_to_quat(a,phi,q);
}
 
Took a look, came away none the wiser. Peered in my crystal ball, but no clue either.
Really, it is totally unclear what you mean. The model rotates by an certain amount ? Is that bad ?

I think your best bet would be to isolate this in a minimal one-source C program, stripping away everything that is not relevant, so that someone could build it and see the problem (having been given a proper description and instructions of course).

As an aside, that very process has often led me to find a solution for a problem which I would not otherwise have found.
 
Thank you for your answer. I agree that it is not very clear. Before I create a stripped down version, I will complement the description of the problem first.
It is a win32 gdi program in pure C that controls a cube with the mouse, simulating a trackball. The math came from this site, that is written in javascript. The demo in that page is the desirable effect. In my implementation, when I release the mouse and click again for new dragging, the model makes an apparently random rotation at the moment of mouse down. If it is still unclear I will create the simplified version.
 
Ok, it is starting to make sense now. Too much irrelevant information is sometimes just as bad as too little relevant information 😁 Correctl me if I'm wrong but I think the problem could now be concisely stated like this:
I found a site that uses JavaScript to rotate a 3D model of a cube with the mouse. I ported that to a Win32 GDI program in C, and it works correctly except that at the moment I click the mouse button a seemingly random rotation occurs.
Is that correctly understood ? There seems no reason why clicking the left button should cause a rotation, the WM_LBUTTONDOWN code does not do any such thing. Are you sure the click action does not send two messages ?
 
Hi,

A prepared a full implementation. The dragging of the cube with the mouse is somewhat irregular. It should be intuitive, as in the javascript example.

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;
}
 
Yeah I see what you mean. The cube rotates, but it's not at all a smooth experience. Sometimes it repositions when you press the mouse key, but not consistently so. Also, the cube does seem to lack perspective compares to the one in the JS example.
I'm afraid I have no idea how all this works, there's quite some math behind it. So I can't help you fix this. But I did get a bunch of warnings during build:

Code:
1>WindowsProject1.c
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(72,9): warning C4244: 'return': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(84,16): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(103,16): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(104,9): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(129,6): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(130,8): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(134,7): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(139,9): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(161,17): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(164,12): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(173,8): warning C4244: '=': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(211,26): warning C4244: 'function': conversion from 'float' to 'int', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(211,55): warning C4244: 'function': conversion from 'float' to 'int', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(213,24): warning C4244: 'function': conversion from 'float' to 'int', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(213,53): warning C4244: 'function': conversion from 'float' to 'int', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(226,44): warning C4244: '=': conversion from 'int' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(322,27): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(323,28): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(324,22): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(325,23): warning C4244: 'function': conversion from 'double' to 'float', possible loss of data
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(342,48): warning C4133: 'function': incompatible types - from 'char [13]' to 'LPCWSTR'
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(349,37): warning C4133: '=': incompatible types - from 'char [15]' to 'LPCWSTR'
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(361,1): warning C4133: 'function': incompatible types - from 'char [15]' to 'LPCWSTR'
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(361,1): warning C4133: 'function': incompatible types - from 'char [8]' to 'LPCWSTR'
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(372,19): warning C4133: 'function': incompatible types - from 'char [7]' to 'LPCWSTR'
1>D:\dotnet\projects\WindowsProject1\WindowsProject1.c(381,12): warning C4244: 'return': conversion from 'WPARAM' to 'int', possible loss of data

Did you get those as well ? If so, could be worth following up on. Although I don't think it will help somehow.
 
Hi,

In my Dell PC XPS with Windows 11 installed and using the Eclipse CDT IDE, I get a seamless compilation:

17:03:46 **** Rebuild of configuration Debug for project attempt1 ****
Info: Internal Builder is used for build
gcc -O0 -g3 -Wall -c -fmessage-length=0 -o attempt1.o "..\\attempt1.c"
gcc -o attempt1.exe attempt1.o -lgdi32

17:03:48 Build Finished. 0 errors, 0 warnings. (took 1s.162ms)

The GCC compiler is MingW.

I also have an improved version that corrects the concatenation of click and drag operations, but it is not perfect yet. I would highly appreciate it if someone could examine an additional flaw and provide a remedy.

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;
}
 
Last edited:

New Threads

Latest posts

Buy us a coffee!

Back
Top Bottom