/**************************************************************************

    CUBE.CPP - A spinning cube demo for WinG

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1994 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and 
    distribute the Sample Files (and/or any modified version) in 
    any way you find useful, provided that you agree that 
    Microsoft has no warranty obligations or liability for any 
    Sample Application Files which are modified. 

 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <wing.h>

#include "cube.hpp"
#include "dumb3d.hpp"
#include "..\utils\utils.h"

#if defined(WIN32)
#define _export
#endif

/**************************************************************************
  Global Variables
 **************************************************************************/

static char szAppName[]="Spinning Cube";

//*** Global Windows needs
static HINSTANCE  hInstApp;
static BOOL       fAppActive;
static HWND       hwndApp;
static HPALETTE   hpalApp = 0;
static HDC        hdcWinG;
static HBITMAP    OldBitmap;
static HBITMAP    WinGBitmap;

struct
{
  BITMAPINFOHEADER  Header;
  RGBQUAD           aColorTable[256];

} HeaderAndPalette =
{
  sizeof(BITMAPINFOHEADER),
  50, 50,
  1, 8,
  BI_RGB,
  0, 0, 0, 0, 0
};


static int        DibWidth, DibHeight;

//*** Cube vertices, normals, shades, and modeling transform
static point_4 CubeVertices[8] =
{
  point_4( -10,  10, -10 ),
  point_4( -10,  10,  10 ),
  point_4(  10,  10,  10 ),
  point_4(  10,  10, -10 ),
  point_4(  10, -10, -10 ),
  point_4(  10, -10,  10 ),
  point_4( -10, -10,  10 ),
  point_4( -10, -10, -10 )
};
static vector_4   CubeSurfaceNormals[6];
static real       CubeSurfaceShades[6];
static matrix_4x4 CubeTransform;

//*** Cube edges - ordered indices into the vertex array
const int CubeFaces[6][4] =
{
  0, 1, 2, 3,
  2, 1, 6, 5,
  3, 2, 5, 4,
  0, 3, 4, 7,
  1, 0, 7, 6,
  4, 5, 6, 7
};

//*** Cube colors - one RGB color per surface
const unsigned char CubeColors[6][3] =
{
  240,  20,  20,    // Unsaturated Red
   20, 240,  20,    // Unsaturated Green
   20,  20, 240,    // Unsaturated Blue
  128,  64,   0,    // Brown
  240,  20, 240,    // Unsaturated Magenta
  240, 240,  20     // Unsaturated Yellow
};

//*** Lighting
vector_4   LightSourceDirection;
const real AmbientLight = 0.2;

//*** Viewing and perspective
static matrix_4x4  ViewPerspective;
static point_4     Viewpoint(60, 60, 60);
static vector_4    Up(0, 1, 0);
static point_4     Origin;

//*** Interaction
static real   XMove,YMove;
static short  gSpinFlag = 1;

//*** Dithering
static int DitherType = 0;
static int Monochrome = 0;

/**************************************************************************
   Internal function declarations
 **************************************************************************/

LONG FAR PASCAL _export  AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
void  AppExit(void);
BOOL  AppIdle(void);
void  AppPaint(HWND hwnd, HDC hdc);

void  TransformCube(matrix_4x4 const &Transform);
void  ProjectAndDrawCube(HDC hdc, int XOffset, int YOffset);

/**************************************************************************
  AppAbout

  Description:
    This function handles messages belonging to the "About" dialog box.
  The only message that it looks for is WM_COMMAND, indicating the user
  has pressed the "OK" button.
 **************************************************************************/

BOOL FAR PASCAL _export AppAbout(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/**************************************************************************
  AppInit

  Description:
    This is called when the application is first loaded. It initializes
  all variables, registers the window class, and creates the main app
  window.
 **************************************************************************/

BOOL AppInit(HINSTANCE hInst,HINSTANCE hPrev,int sw,LPSTR szCmdLine)
{
  WNDCLASS cls;

  /* Save instance handle for DialogBoxes */
  hInstApp = hInst;

// Clear the System Palette so that WinG blting runs at full speed.
  ClearSystemPalette();

  if (!hPrev)
  {
    //***  Register a class for the main application window
    cls.hCursor        = LoadCursor(0,IDC_ARROW);

    //*** Just for fun, we'll draw our own spinning cube icon.
    cls.hIcon          = 0;
    cls.lpszMenuName   = "AppMenu";
    cls.lpszClassName  = szAppName;
    cls.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);
    cls.hInstance      = hInst;
    cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW;
    cls.lpfnWndProc    = (WNDPROC)AppWndProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = 0;

    if (!RegisterClass(&cls))
      return FALSE;
  }

  //*** Set and normalize the light source
  LightSourceDirection = vector_4(50, 30, -15);
  LightSourceDirection.Normalize();

  //*** Distance to view plane:
  ViewPerspective.SetElement(3, 2, 1/300.0);
  ViewPerspective.SetElement(3, 3, 0);

  //*** Viewport scaling - some arbitrary number like 3.5 will do
  ViewPerspective.SetElement(0, 0, 3.5);
  ViewPerspective.SetElement(1, 1, 3.5);

  //*** Calculate the initial normals and shades
  TransformCube(CubeTransform);

  //*** Then generate an interesting rotation for the spin
  CubeTransform.ConcatenateYRotation(6.0);
  CubeTransform.ConcatenateXRotation(3.5);
  CubeTransform.ConcatenateZRotation(2.0);

  hwndApp = CreateWindow (szAppName,   // Class name
                  szAppName,           // Caption
                  WS_OVERLAPPED |
                  WS_CAPTION |
                  WS_SYSMENU |
                  WS_MINIMIZEBOX,      // Style bits
                  CW_USEDEFAULT, 0,    // Position
                  350,350,             // Size
                  0,                   // Parent window (no parent)
                  0,                   // use class menu
                  hInst,               // handle to window instance
                  0                    // no params to pass on
                  );
  hdcWinG = WinGCreateDC();

  ShowWindow(hwndApp,sw);

  //*** Check the default dither selection
  HMENU hMenu = GetMenu(hwndApp);
  CheckMenuItem(hMenu, MENU_DISPERSED8x8, MF_CHECKED);
  CheckMenuItem(hMenu, MENU_SPIN, MF_CHECKED);

  return TRUE;
}

/**************************************************************************
  WinMain

  Description:
    The main procedure for the App.  After initializing, it just goes
  into a message-processing loop until it gets a WM_QUIT message.
 **************************************************************************/

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
  MSG     msg;

  //*** Call initialization procedure
  if (!AppInit(hInst,hPrev,sw,szCmdLine))
    return FALSE;

    //*** Polling messages from event queue until quit
  for (;;)
  {
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
      if (msg.message == WM_QUIT)
        break;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    else
    {
      if (AppIdle())
        WaitMessage();
    }
  }

  return msg.wParam;
}

/**************************************************************************
  AppIdle

  Description:
 **************************************************************************/

BOOL AppIdle()
{
  //*** Spin while the app is active, lbutton is up, and spinning is on.
  //*** Spin while the app is iconized.
  if ( (gSpinFlag && fAppActive && GetKeyState(VK_LBUTTON) >= 0)
      || IsIconic(hwndApp))
  {
    //*** If the app is active, spin the cube and redraw
    TransformCube(CubeTransform);
    HDC hdc = GetDC(hwndApp);
    if (hpalApp)
    {
      SelectPalette(hdc, hpalApp, FALSE);
      RealizePalette(hdc);
    }
    AppPaint(hwndApp, hdc);
    ReleaseDC(hwndApp, hdc);
    return FALSE;
  }
  else
  {
    //*** Don't do anything when not the active app
    return TRUE;
  }
}

/**************************************************************************
  AppPaint

  Description:
    The paint function. Draws the centered cube in the offscreen DIBDC,
  then copies it to the screen using WinGBitBlt or WinGStretchBlt.
 **************************************************************************/

void AppPaint(HWND hwnd, HDC hdc)
{
  //*** Clear the DIBDC buffer to white
  PatBlt(hdcWinG, 0, 0, DibWidth, DibHeight, WHITENESS);

  //*** Move the viewpoint according to the mouse movement
  //*** Rotate it around the Y and X axes
  if(YMove || XMove)
  {
    matrix_4x4 Movement;
    Movement.ConcatenateYRotation(-YMove);
    Movement.ConcatenateXRotation(XMove);

    XMove = YMove = 0;

    TransformCube(Movement);
  }

  //*** and GO!
  ProjectAndDrawCube(hdcWinG, DibWidth/2, DibHeight/2);

  RECT rc;
  GetClientRect(hwndApp, &rc);

  //*** Flip the buffers using WinGBitBlt or WinGStretchBlt
  if (IsIconic(hwndApp))
  {
    WinGStretchBlt(hdc,0,0,rc.right,rc.bottom,hdcWinG,0,0,
        DibWidth,DibHeight);
  }
  else
    WinGBitBlt(hdc,0,0,rc.right,rc.bottom,hdcWinG,0,0);
}

/**************************************************************************
  AppWndProc

  Description:
    Main window proc. Standard Windows fare.
 **************************************************************************/

LONG FAR PASCAL _export AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  PAINTSTRUCT ps;
  HDC hdc;
  BOOL f;
  HMENU hMenu;
  static int LastX, LastY;

  switch (msg)
  {
    case WM_CREATE:
    {
      //*** Use the halftone palette to make things pretty
      hpalApp = WinGCreateHalftonePalette();
      GetPaletteEntries(hpalApp,0,256,
        (PALETTEENTRY far *)HeaderAndPalette.aColorTable);

      for(int Counter = 0;Counter < 256;Counter++)
      {
        // PALETTEENTRYs and RGBQUADs are backwards

        BYTE Temp = HeaderAndPalette.aColorTable[Counter].rgbBlue;

        HeaderAndPalette.aColorTable[Counter].rgbBlue =
            HeaderAndPalette.aColorTable[Counter].rgbRed;

        HeaderAndPalette.aColorTable[Counter].rgbRed = Temp;
      }
      break;
    }
    case WM_ACTIVATEAPP:
      //*** Keep track of whether or not the app is in the foreground
      fAppActive = (BOOL)wParam;
      break;

    case WM_COMMAND:
      switch(wParam)
      {
        case MENU_ABOUT:
          DialogBox(hInstApp, "AppAbout", hwnd, (DLGPROC)AppAbout);
          break;

        case MENU_EXIT:
          PostMessage(hwnd, WM_CLOSE, 0, 0L);
          break;

        case MENU_SPIN:
          //*** Toggle the spin flag and the check mark
          hMenu = GetMenu(hwnd);
          if (gSpinFlag)
          {
            gSpinFlag = 0;
            CheckMenuItem(hMenu, MENU_SPIN, MF_UNCHECKED);
          }
          else
          {
            gSpinFlag = 1;
            CheckMenuItem(hMenu, MENU_SPIN, MF_CHECKED);
          }
          break;

        case MENU_DISPERSED8x8:
        case MENU_DISPERSED4x4:
        case MENU_CLUSTERED4x4:
		  case MENU_UNDITHERED:
          hMenu = GetMenu(hwnd);

          //*** Uncheck the current selection
          CheckMenuItem(hMenu, MENU_DISPERSED8x8 + DitherType,
            MF_UNCHECKED);

          //*** Get the new selection and check it
          DitherType = wParam - MENU_DISPERSED8x8;
          CheckMenuItem(hMenu, wParam, MF_CHECKED);

          //*** Redraw
          InvalidateRect(hwnd, 0, FALSE);
          UpdateWindow(hwnd);
          break;

		  case MENU_MONOCHROME:
          hMenu = GetMenu(hwnd);

          Monochrome = (Monochrome == 1) ? 0 : 1;
          CheckMenuItem(hMenu, wParam,
            (Monochrome == 1) ? MF_CHECKED : MF_UNCHECKED);

          //*** Redraw
          InvalidateRect(hwnd, 0, FALSE);
          UpdateWindow(hwnd);
          break;
      }
      return 0L;

    case WM_DESTROY:
      //*** Clean up before leaving
      if (hpalApp)
        DeleteObject(hpalApp);
      if (hdcWinG)
      {
        SelectObject(hdcWinG,OldBitmap);
        DeleteObject(WinGBitmap);
        DeleteDC(hdcWinG);
      }

      PostQuitMessage(0);
      break;

    case WM_LBUTTONDOWN:
      //*** Get the start location for mouse rotations
      LastX = LOWORD(lParam);
      LastY = HIWORD(lParam);
      break;

    case WM_MOUSEMOVE:
      //*** While the mouse button is down, keep track of movement
      //*** to update the eye position on AppPaint.
      if(GetKeyState(VK_LBUTTON) < 0)
      {
        int X = LOWORD(lParam);
        int Y = HIWORD(lParam);

        YMove = X - LastX;
        XMove = Y - LastY;

        LastX = X;
        LastY = Y;

        InvalidateRect(hwnd, 0, FALSE);
        UpdateWindow(hwnd);
      }
      break;

    case WM_PALETTECHANGED:
      if ((HWND)wParam == hwnd)
        break;
      //*** Else fall through to WM_QUERYNEWPALETTE

    case WM_QUERYNEWPALETTE:
      hdc = GetDC(hwnd);
      if (hpalApp)
        SelectPalette(hdc, hpalApp, FALSE);
      f = RealizePalette(hdc);
      ReleaseDC(hwnd,hdc);

      if (f)
        InvalidateRect(hwnd, 0, FALSE);
      return f;

     case WM_PAINT:
      hdc = BeginPaint(hwnd,&ps);

      if (hpalApp)
      {
        SelectPalette(hdc, hpalApp, FALSE);
        RealizePalette(hdc);
      }
      AppPaint (hwnd, hdc);

      EndPaint(hwnd,&ps);
      return 0L;

    case WM_SIZE:
      if (wParam != SIZE_MINIMIZED)
      {
        //*** Create a WinGBitmap for the buffer that fills the client area
        if (WinGBitmap)
        {
          SelectObject(hdcWinG,OldBitmap);
          DeleteObject(WinGBitmap);
        }
        RECT rect;
        GetClientRect(hwnd, &rect);

		  //*** Set up the Header for the WinGBitmap
		  WinGRecommendDIBFormat((BITMAPINFO FAR *)&HeaderAndPalette);
        HeaderAndPalette.Header.biWidth = rect.right;
        HeaderAndPalette.Header.biHeight *= rect.bottom;

        WinGBitmap = WinGCreateBitmap(hdcWinG,
          (BITMAPINFO far *)&HeaderAndPalette,0);

        OldBitmap = SelectBitmap(hdcWinG,WinGBitmap);
        DibWidth = rect.right;
        DibHeight = rect.bottom;
      }

      //*** Select a null pen so the polygons aren't outlined
      if (hdcWinG)
        SelectPen(hdcWinG, GetStockObject(NULL_PEN));
      break;
  }

  return DefWindowProc(hwnd,msg,wParam,lParam);
}

/**************************************************************************
  TransformCube

  Description:
    Transforms the cube vertices by the current rotation matrix.
    Recalculates normals and flat shade values for the
  directional light source.
 **************************************************************************/

void TransformCube(matrix_4x4 const &Transform)
{
  int i;

  //*** Transform the cube by the matrix
  for (i = 0; i < 8; ++i)
    CubeVertices[i] = Transform * CubeVertices[i];

  //*** Recalculate normals and shades
  for (i = 0; i < 6; ++i)
  {
    //*** Normals are perpendicular to two edges of the cube
    vector_4 Edge1, Edge2;
    Edge1 = CubeVertices[CubeFaces[i][1]] - CubeVertices[CubeFaces[i][0]];
    Edge2 = CubeVertices[CubeFaces[i][3]] - CubeVertices[CubeFaces[i][0]];
    CubeSurfaceNormals[i] = CrossProduct(Edge1, Edge2);
    CubeSurfaceNormals[i].Normalize();

    //*** Cosine shading based on the surface normal, clamped to [0, 1]
    real Shade = DotProduct(CubeSurfaceNormals[i], LightSourceDirection);
    Shade = Shade + AmbientLight;
    if (Shade < 0) Shade = 0;
    else if (Shade > 1.0) Shade = 1.0;
    CubeSurfaceShades[i] = Shade;
  }
}

/**************************************************************************
  ProjectAndDrawCube

  Description:
    Projects the cube vertices for the current viewpoint then culls
  in screen space and draws into the DC. In this case, the DC is a DIBDC.
 **************************************************************************/

void ProjectAndDrawCube(HDC hdc, int XOffset, int YOffset)
{
  //*** Create a viewing transform for the current eye position
  vector_4 ViewDirection = Origin - Viewpoint;
  ViewDirection.Normalize();
  view_transform View(Viewpoint, ViewDirection, Up);

  //*** Transform and project the vertices into screen space
  int i;
  POINT aScreenVertices[8];
  for (i = 0; i < 8; ++i)
  {
    point_4 Temp = View * CubeVertices[i];
    Temp = ViewPerspective * Temp;
    Temp.Homogenize();

    aScreenVertices[i].x = (int)Temp.GetX() + XOffset;
    aScreenVertices[i].y = (int)Temp.GetY() + YOffset;
  }

  for (i = 0; i < 6; ++i)
  {
    //*** Standard culling operation based on the z value of the
    //*** cross product of the edges: are the vertices oriented in the
    //*** counterclockwise or clockwise direction?
    real v1 = aScreenVertices[ CubeFaces[i][2] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real w1 = aScreenVertices[ CubeFaces[i][0] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real v2 = aScreenVertices[ CubeFaces[i][2] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    real w2 = aScreenVertices[ CubeFaces[i][0] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    if ((v1*w2 - v2*w1) <= 0)
      continue;

    //*** Create a brush for the shaded face color using the selected dither

	 HBRUSH hbr;
    static WING_DITHER_TYPE DitherTypeList[3] =
      { WING_DISPERSED_8x8, WING_DISPERSED_4x4, WING_CLUSTERED_4x4 };

    //*** Get the shading colors

	 int Red, Green, Blue;

	 if (Monochrome)
    {
      Red = Green = Blue = real(240) * CubeSurfaceShades[i];
    }
    else
    {
      Red = (real)CubeColors[i][0] * CubeSurfaceShades[i];
      Green = (real)CubeColors[i][1] * CubeSurfaceShades[i];
      Blue = (real)CubeColors[i][2] * CubeSurfaceShades[i];
    }

    //*** Create the dithered or PALETTERGB brush

    COLORREF cr;

    if (DitherType > 2)
    {
      cr = PALETTERGB(Red, Green, Blue);
      hbr = WinGCreateHalftoneBrush(hdcWinG,cr,WING_DISPERSED_4x4);
    }
    else
    {
      cr = RGB(Red, Green, Blue);
      hbr = WinGCreateHalftoneBrush(hdcWinG,cr,DitherTypeList[DitherType]);
    }

    //*** Collect the correct points in an array
    POINT aQuadVertices[4];
    for (int j = 0; j < 4; ++j)
      aQuadVertices[j] = aScreenVertices[ CubeFaces[i][j] ];

    //*** Use GDI to draw the face
    hbr = SelectBrush(hdc, hbr);
    Polygon(hdc, aQuadVertices, 4);
    hbr = SelectBrush(hdc, hbr);
    DeleteObject(hbr);
  }
}
