FLIPCUBE.CPP

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

FLIPCUBE.CPP - A spinning cube demo for DirectDraw

basic page fliping app, just render to the back buffer and flip
that is all I do.

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

(C) Copyright 1995-1997 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.

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

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <dinput.h>
#include <math.h>

#include "flipcube.h"
#include "dumb3d.h"

// code is in tri.cpp
extern void Triangle8(BYTE *p, int next_scan, POINT P0, POINT P1, POINT P2, DWORD c);

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

static char szAppName[]="DirectDraw Spinning Cube";

static HINSTANCE hInstApp;
static BOOL fAppActive;
static BOOL fAppPaused;
static HWND hwndApp;
static HACCEL hAccelApp;
static HFONT AppFont;
static SIZE ScreenSize;
static BOOL fDrawWithGDI;

/**************************************************************************
DirectDraw Globals
**************************************************************************/

IDirectDraw *dd;
IDirectDrawSurface *FrontBuffer;
IDirectDrawSurface *BackBuffer;
IDirectDrawPalette *Palette;

/**************************************************************************
DirectInput Globals
**************************************************************************/
LPDIRECTINPUTlpdi;
LPDIRECTINPUTDEVICE lpdiZoom; // Used for zooming
LPDIRECTINPUTDEVICE lpdiRot; // Use for rotation
BOOL fMouseAcquired = FALSE; // Acquired for rot'n

/**************************************************************************
dumb 3D Globals
**************************************************************************/

//*** 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;

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

LONG CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
BOOL InitDInput(void);
BOOL AppIdle(void);
void RenderFrame(void);

void TransformCube(matrix_4x4 const &Transform);
BOOL ProjectAndDrawCube(HDC hdc, int XOffset, int YOffset);
BOOL ProjectAndDrawCube(IDirectDrawSurface *pdds, 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 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;
}

/**************************************************************************
DDInit

Description:
initialize all the DirectDraw specific stuff
**************************************************************************/

BOOL DDInit()
{
HRESULT err;

err = DirectDrawCreate(NULL, &dd, NULL);

if (err != DD_OK)
return FALSE;

err = dd->SetCooperativeLevel(hwndApp,
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

if (err != DD_OK)
return FALSE;

// NEWNEW init DirectInput iff DDraw inits ok
if(!InitDInput())
return FALSE;

return TRUE;
}

/**************************************************************************
CreateMouse
**************************************************************************/

BOOL CreateMouse(GUID &guid, LPDIRECTINPUTDEVICE& lpdiMouse, DWORD dwAccess)
{
HRESULT err;

err = lpdi->CreateDevice(guid, &lpdiMouse, NULL);

if(err != DI_OK)
{
MessageBox(NULL, "Unable to Create DirectInput Mouse Device",
"DirectDraw Spinning Cube", MB_OK);
goto fail;
}

// Tell DirectInput that we want to receive data in mouse format
err = lpdiMouse->SetDataFormat(&c_dfDIMouse);

if(err != DI_OK)
{
MessageBox(NULL, "Unable to Access DirectInput Device as a mouse",
"DirectDraw Spinning Cube", MB_OK);
goto fail;
}

// set desired access mode
err = lpdiMouse->SetCooperativeLevel(hwndApp, dwAccess);
if(err != DI_OK)
{
// Don't complain if asking for DISCL_NONEXCLUSIVE since
// not all systems support it. (The caller will gracefully
// degrade.)
if (dwAccess & DISCL_EXCLUSIVE) {
MessageBox(NULL, "Unable to set cooperativity level",
"DirectDraw Spinning Cube", MB_OK);
}
goto fail;
}

return TRUE;

fail:;
if (lpdiMouse) lpdiMouse->Release(), lpdiMouse = 0;
return FALSE;
}

/**************************************************************************
InitDInput
**************************************************************************/
BOOL InitDInput(void)
{
HRESULTerr;
GUID guid = GUID_SysMouse;
// Need DirectX 3 or better
err = DirectInputCreate(hInstApp, 0x0300, &lpdi, NULL);

if(err != DI_OK)
{
MessageBox(NULL, "Unable to Create DirectInput Object",
"DirectDraw Spinning Cube", MB_OK);
return FALSE;
}

// Create a mouse for zooming. Zooming is done non-exclusively.
if (!CreateMouse(guid, lpdiZoom, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND))
{
/* Note! This is not a fatal error */
} else {

if (fAppActive) lpdiZoom->Acquire();

}

// Create a mouse for rotation. Rotation is done exclusively.
if (!CreateMouse(guid, lpdiRot, DISCL_EXCLUSIVE | DISCL_FOREGROUND))
{
goto fail;
}

// if we get here, all DirectInput objects were created ok
return TRUE;

fail:
if (lpdiZoom) lpdiZoom->Release(), lpdiZoom = NULL;
if (lpdiRot) lpdiRot ->Release(), lpdiRot = NULL;
if (lpdi) lpdi ->Release(), lpdi = NULL;
return FALSE;

}


/**************************************************************************
DDSetMode
**************************************************************************/

BOOL DDSetMode(int width, int height, int bpp)
{
HRESULT err;

err = dd->SetDisplayMode(width, height, bpp);

if (err != DD_OK)
return FALSE;

ScreenSize.cx = width;
ScreenSize.cy = height;

// get rid of any previous surfaces.
if (BackBuffer) BackBuffer->Release(), BackBuffer = NULL;
if (FrontBuffer) FrontBuffer->Release(), FrontBuffer = NULL;
if (Palette) Palette->Release(), Palette = NULL;

//
// Create surfaces
//
// what we want is a tripple buffered surface in video memory
// so we try to create this first.
//
// if we cant get a triple buffered surface, we try again
// for a double buffered surface (still must be in video memory)
//
// if we cant get a double buffered surface, we try for a double
// buffered surface not being specific about video memory, we will
// get back a main-memory surface, that work use HW page flipping
// but at least we run.
//
// NOTE you need to recreate the surfaces for a new display mode
// they wont work when/if the mode is changed.
//
DDSURFACEDESC ddsd;

ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.dwBackBufferCount = 2;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP |
DDSCAPS_COMPLEX |
DDSCAPS_VIDEOMEMORY;

// try to get a triple buffered video memory surface.
err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);

if (err != DD_OK)
{
// try to get a double buffered video memory surface.
ddsd.dwBackBufferCount = 1;
err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
}

if (err != DD_OK)
{
// settle for a main memory surface.
ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY;
err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
}

if (err != DD_OK)
return FALSE;

// get a pointer to the back buffer
DDSCAPS caps;
caps.dwCaps = DDSCAPS_BACKBUFFER;
err = FrontBuffer->GetAttachedSurface(&caps, &BackBuffer);

if (err != DD_OK)
return FALSE;

// create a palette if we are in a paletized display mode.
//
// NOTE because we want to be able to show dialog boxs and
// use our menu, we leave the windows reserved colors as is
// so things dont look ugly.
//
// palette is setup like so:
//
// 10 windows system colors
// 64 red wash
// 64 grn wash
// 64 blu wash
//
PALETTEENTRY ape[256];
HDC hdc = GetDC(NULL);
if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE)
{
// get the current windows colors.
GetSystemPaletteEntries(hdc, 0, 256, ape);

// make a red, grn, and blu wash for our cube.
for (int i=0; i<64; i++)
{
ape[10+64*0+i].peRed = i * 255/63;
ape[10+64*0+i].peGreen = 0;
ape[10+64*0+i].peBlue = 0;

ape[10+64*1+i].peRed = 0;
ape[10+64*1+i].peGreen = i * 255/63;
ape[10+64*1+i].peBlue = 0;

ape[10+64*2+i].peRed = 0;
ape[10+64*2+i].peGreen = 0;
ape[10+64*2+i].peBlue = i * 255/63;
}

// create the palette.
err = dd->CreatePalette(DDPCAPS_8BIT, ape, &Palette, NULL);

if (err == DD_OK)
{
FrontBuffer->SetPalette(Palette);
}
}
ReleaseDC(NULL, hdc);

if (AppFont)
DeleteObject(AppFont);

AppFont = CreateFont(width < 640 ? 24 : 48,
0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
NONANTIALIASED_QUALITY,
VARIABLE_PITCH,
"Comic Sans MS");

return TRUE;
}


/**************************************************************************
DDTerm
**************************************************************************/

void DDTerm()
{
if (BackBuffer) BackBuffer->Release(), BackBuffer = NULL;
if (FrontBuffer) FrontBuffer->Release(), FrontBuffer = NULL;
if (Palette) Palette->Release(), Palette = NULL;
if (dd) dd->Release(), dd = NULL;
if (lpdi) lpdi->Release(), lpdi = NULL;
}

/**************************************************************************
ModeCallback
**************************************************************************/

#define MAKEMENUITEMDATA(width, height, bpp) \
(width) | ((height) << 12) | ((bpp) << 24)

HRESULT CALLBACK ModeCallback(LPDDSURFACEDESC pdds, LPVOID lParam)
{
HMENU hmenu = (HMENU)lParam;
char ach[80];
int n;
int width = pdds->dwWidth;
int height = pdds->dwHeight;
int bpp = pdds->ddpfPixelFormat.dwRGBBitCount;

n = GetMenuItemCount(hmenu);
wsprintf(ach,"%dx%dx%d",width,height,bpp);
AppendMenu(hmenu,MF_STRING,MENU_MODE+n,ach);

MENUITEMINFO mii;

// pack the mode info into a DWORD and set the extra item data.
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_DATA;
mii.dwItemData = MAKEMENUITEMDATA(width, height, bpp);
SetMenuItemInfo(hmenu, MENU_MODE+n, MF_BYCOMMAND, &mii);

//return S_TRUE to stop enuming modes, S_FALSE to continue
return S_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;

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 = LoadIcon(hInst, "AppIcon");
cls.lpszMenuName = "AppMenu";
cls.lpszClassName = szAppName;
cls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
cls.hInstance = hInst;
cls.style = CS_VREDRAW | CS_HREDRAW;
cls.lpfnWndProc = (WNDPROC)AppWndProc;
cls.cbClsExtra = 0;
cls.cbWndExtra = 0;

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

hAccelApp = LoadAccelerators(hInst, "AppAccel");

//*** 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 = CreateWindowEx(
WS_EX_APPWINDOW,
szAppName, // Class name
szAppName, // Caption
WS_POPUP |
WS_SYSMENU |
WS_CAPTION,
0, 0, // Position
640,480, // Size
0, // Parent window (no parent)
0, // use class menu
hInst, // handle to window instance
0 // no params to pass on
);
ShowWindow(hwndApp,sw);
UpdateWindow(hwndApp);

if (!DDInit())
return FALSE;

// Enumerate all posible display modes, and stick them in our menu.
// we use the extra item DWORD of a menu item to store the mode info
HMENU hmenu = CreatePopupMenu();
dd->EnumDisplayModes(0,NULL,(LPVOID)hmenu,ModeCallback);
AppendMenu(GetMenu(hwndApp),MF_POPUP,(UINT)hmenu,"&Modes");

if (!DDSetMode(640,480,8) &&
!DDSetMode(640,480,16))
return FALSE;

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;

if (!hwndApp || !TranslateAccelerator(hwndApp, hAccelApp, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
if (AppIdle())
WaitMessage();
}
}

DDTerm();
return msg.wParam;
}

/**************************************************************************
AppPause

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

void AppPause(BOOL f)
{
if (f)
{
DDSCAPS caps;
FrontBuffer->GetCaps(&caps);

// if we are in ModeX go back to a windows mode
// so we can see the menu or dialog box.

if (caps.dwCaps & DDSCAPS_MODEX)
{
DDSetMode(640,480,8);
}

// turn off rotation while paused
if (lpdiRot) lpdiRot->Unacquire();
fMouseAcquired = FALSE;

fAppPaused = TRUE;
dd->FlipToGDISurface();
DrawMenuBar(hwndApp);
RedrawWindow(hwndApp, NULL, NULL, RDW_FRAME);
}
else
{
fAppPaused = FALSE;

}
}

/**************************************************************************
MagnifyCube

Description:
Magnify the cube the indicated number of times. A negative number
makes it smaller.
**************************************************************************/

void MagnifyCube(double times)
{
matrix_4x4 m;
double factor = pow(1.5, times);
m.SetElement(0,0,factor);
m.SetElement(1,1,factor);
m.SetElement(2,2,factor);
TransformCube(m);
}

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

return TRUE if the app is idle
return FALSE if the app is not idle.

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

BOOL AppIdle()
{
DIMOUSESTATE dims;

//*** Spin while the app is active, lbutton is up, and spinning is on.

//*** Spin while the app is iconized.
if (fAppActive && !fAppPaused)
{
//*** If the app is active, spin the cube and redraw

// See if any zooming needs to be done.
if(lpdiZoom &&
lpdiZoom->GetDeviceState(sizeof(DIMOUSESTATE), &dims) == DI_OK) {
// 240 units of motion in the Z-axis equals one unit of
// magnification / shrinkage.
if(dims.lZ) {
MagnifyCube(dims.lZ / 240.0);
}
}

if(fMouseAcquired)
{
//** If we have the mouse acquired...

// user spins cube if GetDeviceState succeeds and if the left button (button 0) is held
if(lpdiRot->GetDeviceState(sizeof(DIMOUSESTATE), &dims) == DI_OK)
{
if(dims.rgbButtons[0] & 0x80)
{
if(dims.lX || dims.lY)
{
matrix_4x4 Movement;
Movement.ConcatenateYRotation(dims.lX);
Movement.ConcatenateXRotation(dims.lY);
TransformCube(Movement);
}

}
else
{
// unacquire the mouse
lpdiRot->Unacquire();
fMouseAcquired = FALSE;
}
}
}
else
{
TransformCube(CubeTransform);
}
RenderFrame();
return FALSE;
}
else
{
//*** Don't do anything when not the active app
return TRUE;
}
}

/**************************************************************************
RenderFrame

render the frame into the back buffer and do a page flip.

things to NOTE:

we use the blter to clear the backbuffer, this usualy is a big
win blters are real fast.

we use GDI to draw the frame rate, and info text

we either use GDI to draw the faces of the cube, or our own code
based on the fDrawWithGDI global variable.

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

int FrameRate;
int FrameCount;
int FrameCount0;
DWORD FrameTime;
DWORD FrameTime0;

void RenderFrame()
{
HDC hdc;

//*** always need to handle DDERR_SURFACELOST, this will happen
//*** when we get switched away from.

if (FrontBuffer->IsLost() == DDERR_SURFACELOST)
FrontBuffer->Restore();

//*** use the blter to do a color fill to clear the back buffer

DDBLTFX ddbltfx;
ddbltfx.dwSize = sizeof(ddbltfx);
ddbltfx.dwFillColor = 0;
BackBuffer->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&ddbltfx);

//*** based on the fDrawWithGDI global variable, we either
//*** render the polygons ourself or let GDI do it

BOOL fGDI = fDrawWithGDI;

//*** render the cube with our own code.
//*** we need to do this outside of the GetDC, because we cant
//*** lock a buffer while we have a DC on it.
//*** if ProjectAndDrawCube returns FALSE it did not want to
//*** draw for some reason, so go ahead and use GDI

if (!fGDI)
fGDI = !ProjectAndDrawCube(BackBuffer, ScreenSize.cx/2, ScreenSize.cy/2);

if (BackBuffer->GetDC(&hdc) == DD_OK)
{
//*** use GDI to draw the cube.
if (fGDI)
ProjectAndDrawCube(hdc, ScreenSize.cx/2, ScreenSize.cy/2);

//*** draw stats, like frame number and frame rate

char ach[128];
int len;
static char szHelp[] = "F10=Menu F7=Smaller F8=Larger";

SetBkMode(hdc, TRANSPARENT);
SelectObject(hdc, AppFont);

len = wsprintf(ach, "FPS %02d Frame %05d", FrameRate, FrameCount);

SetTextColor(hdc, RGB(255, 255, 0));
TextOut(hdc, 0, 0, ach, len);
TextOut(hdc, 0, ScreenSize.cy-(ScreenSize.cx<640 ? 24:48),szHelp,sizeof(szHelp)-1);

BackBuffer->ReleaseDC(hdc);
}

//*** we have rendered the backbuffer, call flip so we can see it
FrontBuffer->Flip(NULL, DDFLIP_WAIT);

FrameCount++;
FrameTime = timeGetTime();

if (FrameTime - FrameTime0 > 1000)
{
FrameRate = (FrameCount - FrameCount0) * 1000 / (FrameTime - FrameTime0);
FrameTime0 = FrameTime;
FrameCount0 = FrameCount;
}
}

/**************************************************************************
AppInitMenuPopup

If it's the Cube menu, then check or uncheck the GDI item accordingly.

If it's the mode list popup, then add a MENUBARBREAK as necessary.

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

void AppInitMenuPopup(HWND hwnd, HMENU hmenuPopup, UINT uPos, BOOL fSys)
{
if (fSys) return; /* Don't mess with the sysmenu */

switch (uPos) {
case 0: /* Cube menu */
CheckMenuItem(hmenuPopup, MENU_GDI,
fDrawWithGDI ? MF_CHECKED : MF_UNCHECKED);
break;

case 1: /* Mode menu */
/*
* Compute how many menu items fit on the screen.
*
* Note that we use ScreenSize instead of SM_CYSCREEN.
* This allows us to do the right thing in the face of
* multiple monitors. (ScreenSize is the size of our
* monitor.)
*/
int cmi;

cmi = GetMenuItemCount(hmenuPopup);
if (cmi) {

RECT rcClient;
GetClientRect(hwnd, &rcClient);

RECT rc;
GetMenuItemRect(hwnd, hmenuPopup, 0, &rc);

int dyMenuItem = rc.bottom - rc.top;

/*
* Aargh. If the menu has never appeared yet, USER
* returns an empty rectangle. DON'T DIVIDE BY ZERO!
*
* In such case, we use the height of the menu bar.
* Not perfect, but close enough. And it happens only
* once.
*/
if (dyMenuItem == 0) {
dyMenuItem = GetSystemMetrics(SM_CYMENU);
}

/*
* Aargh. You can't change MF_MENUBARBREAK without
* also changing the text, so we have to get the
* old text and set it back.
*
* While we're here, we may as well put a check-box
* next to the item that matches our current screen res.
*/

HDC hdc = GetDC(0);

DWORD dwItemDataMatch = MAKEMENUITEMDATA(
GetDeviceCaps(hdc, HORZRES),
GetDeviceCaps(hdc, VERTRES),
GetDeviceCaps(hdc, BITSPIXEL) *
GetDeviceCaps(hdc, PLANES));
ReleaseDC(0, hdc);


MENUITEMINFO mii;
TCHAR tsz[80];

mii.cbSize = sizeof(mii);
mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_STATE;
mii.dwTypeData = tsz;

/*
* Compute the number of MI's that fit in our client area.
* Note: Client area, not screen size. This ensures that
* the menu pops in a reasonable location.
*/

int cmiScreen = rcClient.bottom / dyMenuItem;
BOOL bModeMatch = FALSE;
for (int imi = 0; imi < cmi; imi++) {
mii.cch = sizeof(tsz) / sizeof(tsz[0]);
if (GetMenuItemInfo(hmenuPopup, imi, MF_BYPOSITION, &mii)) {
if (imi > 0 && imi % cmiScreen == 0) {
mii.fType |= MFT_MENUBARBREAK;
} else {
mii.fType &= ~MFT_MENUBARBREAK;
}

if (mii.dwItemData == dwItemDataMatch) {
mii.fState |= MFS_CHECKED;
bModeMatch = TRUE;
} else {
mii.fState &= ~MFS_CHECKED;
}

SetMenuItemInfo(hmenuPopup, imi, MF_BYPOSITION, &mii);
}
}
if(bModeMatch == FALSE) _asm int 3

}
break;
}

}

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

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

LONG CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
RECT Rect;

switch (msg)
{
case WM_CREATE:
break;

case WM_ACTIVATEAPP:
//*** Keep track of whether or not the app is in the foreground
fAppActive = (BOOL)wParam;
// re-acquire the zooming controller when we are activated
if (fAppActive) {
if (lpdiZoom) lpdiZoom->Acquire();
} else{ // unacquire everything if app is not active
if (lpdiZoom) lpdiZoom->Unacquire();
if (lpdiRot) lpdiRot->Unacquire();
fMouseAcquired = FALSE;
}
break;

case WM_SETCURSOR:
if (fAppActive && !fAppPaused)
{
SetCursor(NULL);
return 1;
}
break;

case WM_ENTERMENULOOP:
AppPause(TRUE);
break;

case WM_EXITMENULOOP:
AppPause(FALSE);
break;

case WM_INITMENUPOPUP:
AppInitMenuPopup(hwnd, (HMENU)wParam,
(UINT)LOWORD(lParam), (BOOL)HIWORD(lParam));

break; 

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

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

case MENU_LARGER:
MagnifyCube(+1.0);
break;

case MENU_SMALLER:
MagnifyCube(-1.0);
break;

case MENU_GDI:
fDrawWithGDI = !fDrawWithGDI;
break;
}
if (LOWORD(wParam) >= MENU_MODE && LOWORD(wParam) < MENU_MODE+100)
{
MENUITEMINFO mii;

mii.cbSize = sizeof(mii);
mii.fMask = MIIM_DATA;
GetMenuItemInfo(GetMenu(hwnd), LOWORD(wParam), MF_BYCOMMAND, &mii);

DDSetMode(
(mii.dwItemData >> 0) & 0xFFF,
(mii.dwItemData >> 12) & 0xFFF,
(mii.dwItemData >> 24) & 0x0FF);
}
return 0L;

case WM_DESTROY:
// clean up DirectInput objects
if (fMouseAcquired) lpdiRot->Unacquire();
if (lpdiZoom) lpdiZoom->Release(), lpdiZoom = NULL;
if (lpdiRot) lpdiRot ->Release(), lpdiRot = NULL;
if (lpdi) lpdi ->Release(), lpdi = NULL;

hwndApp = NULL;
PostQuitMessage(0);
break;

case WM_PAINT:
break;

case WM_MOVE:
case WM_SIZE:
case WM_DISPLAYCHANGE:
if (fAppActive && !IsIconic(hwnd))
{
SetRect(&Rect, 0, GetSystemMetrics(SM_CYCAPTION), GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
AdjustWindowRectEx(&Rect, WS_POPUP | WS_CAPTION, FALSE, 0);
SetWindowPos(hwnd, NULL, Rect.left, Rect.top, Rect.right-Rect.left, Rect.bottom-Rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
}
break;

case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
if(lpdiRot->Acquire() == DI_OK)
{
fMouseAcquired = TRUE;
}
else
{
// not acquired, mouse will not do anything
}
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 via GDI.
**************************************************************************/

BOOL 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;
}

SelectPen(hdc, GetStockPen(NULL_PEN));

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;

//*** Get the shading colors

int Red, Green, Blue;

Red = (int)(CubeColors[i][0] * CubeSurfaceShades[i]);
Green = (int)(CubeColors[i][1] * CubeSurfaceShades[i]);
Blue = (int)(CubeColors[i][2] * CubeSurfaceShades[i]);

//*** Create the dithered or PALETTERGB brush

COLORREF cr;

cr = RGB(Red, Green, Blue);
hbr = CreateSolidBrush(cr);

//*** 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);
}

return TRUE;
}

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

Description:
Projects the cube vertices for the current viewpoint then culls
in screen space and draws them into a DirectDrawSurface via custom code
**************************************************************************/

BOOL ProjectAndDrawCube(IDirectDrawSurface *pdds, int XOffset, int YOffset)
{
//*** Lock the DirectDraw surface
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);

if (pdds->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL) != DD_OK)
return FALSE;

//*** This code only works for 8bpp
if (ddsd.ddpfPixelFormat.dwRGBBitCount != 8)
{
pdds->Unlock(NULL);
return FALSE;
}

//*** 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;

//*** !!! OUR CODE DOES NOT CLIP, SO FAIL IF WE NEED CLIPPING
if (aScreenVertices[i].x < 0 || aScreenVertices[i].x >= ScreenSize.cx ||
aScreenVertices[i].y < 0 || aScreenVertices[i].y >= ScreenSize.cy)
{
pdds->Unlock(NULL);
return FALSE;
}
}

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;

//*** Get the shading color, palette is setup like so:
//*** 10 system, 64 red, 64 green, 64 blue

BYTE color;

if (CubeColors[i][0] >= 128)
color = (BYTE)(10 + 0*64 + (63 * CubeSurfaceShades[i]));
else if (CubeColors[i][1] >= 128)
color = (BYTE)(10 + 1*64 + (63 * CubeSurfaceShades[i]));
else
color = (BYTE)(10 + 2*64 + (63 * CubeSurfaceShades[i]));

//*** Use code in tri.cpp draw the face

Triangle8((BYTE*)ddsd.lpSurface, ddsd.lPitch,
aScreenVertices[CubeFaces[i][0]],
aScreenVertices[CubeFaces[i][1]],
aScreenVertices[CubeFaces[i][2]],
color);

Triangle8((BYTE*)ddsd.lpSurface, ddsd.lPitch,
aScreenVertices[CubeFaces[i][2]],
aScreenVertices[CubeFaces[i][3]],
aScreenVertices[CubeFaces[i][0]],
color);
}

//*** Never ever forget to unlock!
pdds->Unlock(NULL);
return TRUE;
}