// Magic Software, Inc.
// http://www.magic-software.com
// http://www.wild-magic.com
// Copyright (c) 2004.  All Rights Reserved
//
// The Wild Magic Library (WM3) source code is supplied under the terms of
// the license agreement http://www.wild-magic.com/License/WildMagic3.pdf and
// may not be copied or disclosed except in accordance with the terms of that
// agreement.

#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>

// window dimensions
static int gs_iWidth = 640;
static int gs_iHeight = 480;
static HDC gs_hWindowDC = (HDC)0;

// view frustum
static double gs_fLFrustum = -0.5500;
static double gs_fRFrustum = +0.5500;
static double gs_fBFrustum = -0.4125;
static double gs_fTFrustum = +0.4125;
static double gs_fNFrustum = +1.0;
static double gs_fFFrustum = +100.0;

// viewport
static int gs_iXPort = 0;
static int gs_iYPort = 0;
static int gs_iWPort = gs_iWidth;
static int gs_iHPort = gs_iHeight;

// camera world coordinates
static double gs_adEye[3] = { 0.0, 0.0, 4.0 };
static double gs_adDir[3] = { 0.0, 0.0, -1.0 };
static double gs_adUp[3] = { 0.0, 1.0, 0.0 };
static double gs_adRight[3] = { 1.0, 0.0, 0.0 };

// vertex-colored triangle
static float gs_afVertex0[3] = { 1.0f, 0.0f, 0.0f };
static float gs_afVertex1[3] = { -1.0f, 1.0f, 0.0f };
static float gs_afVertex2[3] = { -1.0f, -1.0f, 0.0f };
static float gs_afColor0[3] = { 1.0f, 0.0f, 0.0f };  // red
static float gs_afColor1[3] = { 0.0f, 1.0f, 0.0f };  // green
static float gs_afColor2[3] = { 0.0f, 0.0f, 1.0f };  // blue

// translation vector for triangle
static float gs_afTranslate[3] =
{
    0.0f, 0.0f, 0.0f
};

// rotation matrix for triangle
static float gs_aafRotate[3][3] =
{
    {1.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f},
    {0.0f, 0.0f, 1.0f}
};

// transformation matrix for triangle (in OpenGL format)
static float gs_afMatrix[16] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

// for incremental translations
static float gs_fDPosition = 0.1f;

// for incremental rotations
static float gs_afAngle = 0.1f;
static float gs_fCos = cosf(gs_afAngle);
static float gs_fSin = sinf(gs_afAngle);

//----------------------------------------------------------------------------
static void DrawIt ()
{
    // set the entire window to the background color
    glClear(GL_COLOR_BUFFER_BIT);

    // double-sided triangles
    glDisable(GL_CULL_FACE);

    // set the model-to-world transformation
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glMultMatrixf(gs_afMatrix);

    // draw the triangle
    glBegin(GL_POLYGON);
    glColor3f(gs_afColor0[0],gs_afColor0[1],gs_afColor0[2]);
    glVertex3f(gs_afVertex0[0],gs_afVertex0[1],gs_afVertex0[2]);
    glColor3f(gs_afColor1[0],gs_afColor1[1],gs_afColor1[2]);
    glVertex3f(gs_afVertex1[0],gs_afVertex1[1],gs_afVertex1[2]);
    glColor3f(gs_afColor2[0],gs_afColor2[1],gs_afColor2[2]);
    glVertex3f(gs_afVertex2[0],gs_afVertex2[1],gs_afVertex2[2]);
    glEnd();

    // restore the previous transformation
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    // copy the back buffer into the front buffer
    SwapBuffers(gs_hWindowDC);
}
//----------------------------------------------------------------------------
LRESULT CALLBACK WinProc (HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    switch ( uiMsg ) 
    {
        case WM_CHAR:
        {
            // triangle motion
            float fTmp0, fTmp1;
            int i;

            unsigned char ucKey = (unsigned char)(char)wParam;
            switch ( ucKey )
            {
            // rotations
            case 'r':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[1][i] +
                        gs_fSin*gs_aafRotate[2][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[2][i] -
                        gs_fSin*gs_aafRotate[1][i];
                    gs_aafRotate[1][i] = fTmp0;
                    gs_aafRotate[2][i] = fTmp1;
                }
                break;
            case 'R':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[1][i] -
                        gs_fSin*gs_aafRotate[2][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[2][i] +
                        gs_fSin*gs_aafRotate[1][i];
                    gs_aafRotate[1][i] = fTmp0;
                    gs_aafRotate[2][i] = fTmp1;
                }
                break;
            case 'u':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[0][i] +
                        gs_fSin*gs_aafRotate[2][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[2][i] -
                        gs_fSin*gs_aafRotate[0][i];
                    gs_aafRotate[0][i] = fTmp0;
                    gs_aafRotate[2][i] = fTmp1;
                }
                break;
            case 'U':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[0][i] -
                        gs_fSin*gs_aafRotate[2][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[2][i] +
                        gs_fSin*gs_aafRotate[0][i];
                    gs_aafRotate[0][i] = fTmp0;
                    gs_aafRotate[2][i] = fTmp1;
                }
                break;
            case 'd':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[0][i] +
                        gs_fSin*gs_aafRotate[1][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[1][i] -
                        gs_fSin*gs_aafRotate[0][i];
                    gs_aafRotate[0][i] = fTmp0;
                    gs_aafRotate[1][i] = fTmp1;
                }
                break;
            case 'D':
                for (i = 0; i < 3; i++)
                {
                    fTmp0 =
                        gs_fCos*gs_aafRotate[0][i] -
                        gs_fSin*gs_aafRotate[1][i];
                    fTmp1 =
                        gs_fCos*gs_aafRotate[1][i] +
                        gs_fSin*gs_aafRotate[0][i];
                    gs_aafRotate[0][i] = fTmp0;
                    gs_aafRotate[1][i] = fTmp1;
                }
                break;

            // translations
            case 'x':
                gs_afTranslate[0] -= gs_fDPosition;
                break;
            case 'X':
                gs_afTranslate[0] += gs_fDPosition;
                break;
            case 'y':
                gs_afTranslate[1] -= gs_fDPosition;
                break;
            case 'Y':
                gs_afTranslate[1] += gs_fDPosition;
                break;
            case 'z':
                gs_afTranslate[2] -= gs_fDPosition;
                break;
            case 'Z':
                gs_afTranslate[2] += gs_fDPosition;
                break;

            default:
                return 0;
            }

            // update the model-to-world transformation
            gs_afMatrix[ 0] = gs_aafRotate[0][0];
            gs_afMatrix[ 1] = gs_aafRotate[1][0];
            gs_afMatrix[ 2] = gs_aafRotate[2][0];
            gs_afMatrix[ 3] = 0.0f;
            gs_afMatrix[ 4] = gs_aafRotate[0][1];
            gs_afMatrix[ 5] = gs_aafRotate[1][1];
            gs_afMatrix[ 6] = gs_aafRotate[2][1];
            gs_afMatrix[ 7] = 0.0f;
            gs_afMatrix[ 8] = gs_aafRotate[0][2];
            gs_afMatrix[ 9] = gs_aafRotate[1][2];
            gs_afMatrix[10] = gs_aafRotate[2][2];
            gs_afMatrix[11] = 0.0f;
            gs_afMatrix[12] = gs_afTranslate[0];
            gs_afMatrix[13] = gs_afTranslate[1];
            gs_afMatrix[14] = gs_afTranslate[2];
            gs_afMatrix[15] = 1.0f;
            return 0;
        }
        case WM_KEYDOWN:
        {
            // camera motion
            int iVirtKey = int(wParam);
            double adTmp0[3], adTmp1[3];
            int i;

            switch ( iVirtKey )
            {
            case VK_UP:     // up arrow (move forward)
                for (i = 0; i < 3; i++)
                    gs_adEye[i] += gs_fDPosition*gs_adDir[i];
                break;
            case VK_DOWN:   // down arrow (move backward)
                for (i = 0; i < 3; i++)
                    gs_adEye[i] -= gs_fDPosition*gs_adDir[i];
                break;
            case VK_LEFT:   // left arrow (rotate about up - look left)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adRight[i] + gs_fSin*gs_adDir[i];
                    adTmp1[i] = gs_fCos*gs_adDir[i] - gs_fSin*gs_adRight[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adRight[i] = adTmp0[i];
                    gs_adDir[i] = adTmp1[i];
                }
                break;
            case VK_RIGHT:  // right arrow (rotate about up - look right)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adRight[i] - gs_fSin*gs_adDir[i];
                    adTmp1[i] = gs_fCos*gs_adDir[i] + gs_fSin*gs_adRight[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adRight[i] = adTmp0[i];
                    gs_adDir[i] = adTmp1[i];
                }
                break;
            case VK_PRIOR:  // page up (rotate about right - look up)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adDir[i] + gs_fSin*gs_adUp[i];
                    adTmp1[i] = gs_fCos*gs_adUp[i] - gs_fSin*gs_adDir[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adDir[i] = adTmp0[i];
                    gs_adUp[i] = adTmp1[i];
                }
                break;
            case VK_NEXT:   // page down (rotate about right - look down)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adDir[i] - gs_fSin*gs_adUp[i];
                    adTmp1[i] = gs_fCos*gs_adUp[i] + gs_fSin*gs_adDir[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adDir[i] = adTmp0[i];
                    gs_adUp[i] = adTmp1[i];
                }
                break;
            case VK_HOME:   // home (rotate about direction - roll left)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adRight[i] + gs_fSin*gs_adUp[i];
                    adTmp1[i] = gs_fCos*gs_adUp[i] - gs_fSin*gs_adRight[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adRight[i] = adTmp0[i];
                    gs_adUp[i] = adTmp1[i];
                }
                break;
            case VK_END:    // end (rotate about direction - roll right)
                for (i = 0; i < 3; i++)
                {
                    adTmp0[i] = gs_fCos*gs_adRight[i] - gs_fSin*gs_adUp[i];
                    adTmp1[i] = gs_fCos*gs_adUp[i] + gs_fSin*gs_adRight[i];
                }
                for (i = 0; i < 3; i++)
                {
                    gs_adRight[i] = adTmp0[i];
                    gs_adUp[i] = adTmp1[i];
                }
                break;
            default:
                return 0;
            }

            // set the camera world coordinate system
            double adLookAt[3] =
            {
                gs_adEye[0]+gs_adDir[0],
                gs_adEye[1]+gs_adDir[1],
                gs_adEye[2]+gs_adDir[2]
            };
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            gluLookAt(gs_adEye[0],gs_adEye[1],gs_adEye[2],adLookAt[0],
                adLookAt[1],adLookAt[2],gs_adUp[0],gs_adUp[1],gs_adUp[2]);
            return 0;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }

    return DefWindowProc(hWnd,uiMsg,wParam,lParam);
}
//----------------------------------------------------------------------------
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE, LPSTR acArgument, int)
{
    // register the window class
    static char s_acWindowClass[] = "Wild Magic Application";
    WNDCLASS wc;
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc   = WinProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszClassName = s_acWindowClass;
    wc.lpszMenuName  = NULL;
    RegisterClass(&wc);

    // require the window to have the specified client area
    RECT kRect = { 0, 0, gs_iWidth-1, gs_iHeight-1 };
    AdjustWindowRect(&kRect,WS_OVERLAPPEDWINDOW,false);

    // create the application window
    static char s_acWindowTitle[] = "Draw Triangle";
    int iXPos = 0, iYPos = 0;
    int iWidth = kRect.right - kRect.left + 1;
    int iHeight = kRect.bottom - kRect.top + 1;
    HWND hWnd = CreateWindow(s_acWindowClass,s_acWindowTitle,
        WS_OVERLAPPEDWINDOW,iXPos,iYPos,iWidth,iHeight,(HWND)0,(HMENU)0,
        hInstance,NULL);

    // create a window for rendering
    gs_hWindowDC = GetDC(hWnd);

    // select format for drawing surface
    PIXELFORMATDESCRIPTOR kPFD;
    memset(&kPFD,0,sizeof(PIXELFORMATDESCRIPTOR));
    kPFD.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    kPFD.nVersion = 1;
    kPFD.dwFlags =
        PFD_DRAW_TO_WINDOW |
        PFD_SUPPORT_OPENGL |
        PFD_GENERIC_ACCELERATED |
        PFD_DOUBLEBUFFER;
    kPFD.iPixelType = PFD_TYPE_RGBA;
    kPFD.cColorBits = 24;  // 24-bit colors for front/back buffers
    kPFD.cDepthBits = 16;  // 16-bit depth buffer
    kPFD.cStencilBits = 8; // 8-bit stencil buffer

    // set the pixel format for the rendering context
    int iPixelFormat = ChoosePixelFormat(gs_hWindowDC,&kPFD);
    if ( iPixelFormat == 0 )
    {
        ReleaseDC(hWnd,gs_hWindowDC);
        return -1;
    }

    BOOL bSuccess = SetPixelFormat(gs_hWindowDC,iPixelFormat,&kPFD);
    if ( !bSuccess )
    {
        ReleaseDC(hWnd,gs_hWindowDC);
        return -2;
    }

    // create an OpenGL context
    HGLRC hWindowRC = wglCreateContext(gs_hWindowDC);
    if ( !hWindowRC )
    {
        ReleaseDC(hWnd,gs_hWindowDC);
        return -3;
    }

    bSuccess = wglMakeCurrent(gs_hWindowDC,hWindowRC);
    if ( !bSuccess )
    {
        wglDeleteContext(hWindowRC);
        ReleaseDC(hWnd,gs_hWindowDC);
        return -4;
    }

    // background color is grey
    glClearColor(0.75f,0.75f,0.75f,1.0f);

    // set up for perspective projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(gs_fLFrustum,gs_fRFrustum,gs_fBFrustum,gs_fTFrustum,
        gs_fNFrustum,gs_fFFrustum);

    // set the viewport so that the entire screen is drawn to
    glViewport(gs_iXPort,gs_iYPort,gs_iWPort,gs_iHPort);

    // set the camera world coordinate system
    double adLookAt[3] =
    {
        gs_adEye[0]+gs_adDir[0],
        gs_adEye[1]+gs_adDir[1],
        gs_adEye[2]+gs_adDir[2]
    };
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(gs_adEye[0],gs_adEye[1],gs_adEye[2],adLookAt[0],adLookAt[1],
        adLookAt[2],gs_adUp[0],gs_adUp[1],gs_adUp[2]);

    // display the window
    ShowWindow(hWnd,SW_SHOW);
    UpdateWindow(hWnd);

    // start the message pump
    MSG kMsg;
    while ( TRUE )
    {
        if ( PeekMessage(&kMsg,(HWND)0,0,0,PM_REMOVE) )
        {
            if ( kMsg.message == WM_QUIT )
                break;

            HACCEL hAccel = (HACCEL)0;
            if ( !TranslateAccelerator(hWnd,hAccel,&kMsg) )
            {
                TranslateMessage(&kMsg);
                DispatchMessage(&kMsg);
            }
        }
        else
        {
            // idle loop
            DrawIt();
        }
    }

    // clean up
    wglDeleteContext(hWindowRC);
    ReleaseDC(hWnd,gs_hWindowDC);
    return 0;
}
//----------------------------------------------------------------------------
