/******************************Module*Header*******************************\
* Module Name: ss3dfo.c
*
* Dispatcher and dialog box for the OpenGL-based 3D Flying Objects screen
* saver.
*
* Created: 18-May-1994 14:54:59
*
* Copyright (c) 1994-1995 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys\timeb.h>
#include <time.h>
#include "ssopengl.h"
#include "image.h"

// Global strings.
#define GEN_STRING_SIZE 64
TCHAR szScreenSaverTitle[GEN_STRING_SIZE];
TCHAR szTextureDialogTitle[GEN_STRING_SIZE];
TCHAR szTextureFilter[GEN_STRING_SIZE];
TCHAR szBmp[GEN_STRING_SIZE];
TCHAR szDotBmp[GEN_STRING_SIZE];
TCHAR szClassName[] = TEXT("3DFOScreenSaverClass");

// Global message loop variables.
HWND hwndOpenGL = NULL;
HGLRC gHrc = (HGLRC) 0;
HPALETTE ghpalOld, ghPalette = (HPALETTE) 0;
MATERIAL Material[16];
UINT guiOldStaticUse = SYSPAL_STATIC;
BOOL gbUseStatic = FALSE;
UINT idTimer = 0;
#ifdef MEMDEBUG
ULONG totalMem = 0;
#endif

// Global string buffers for message box.

TCHAR gach[2 * MAX_PATH];      // May contain a pathname
TCHAR gachFormat[MAX_PATH];
TCHAR gachTitle[MAX_PATH];

// Global screen saver settings.

void (*updateSceneFunc)(HWND, int); // current screen saver update function
void (*delSceneFunc)(void);         // current screen saver deletion function
BOOL bColorCycle = FALSE;           // color cycling flag
BOOL bSmoothShading = TRUE;         // smooth shading flag
UINT uSize = 100;                   // object size
float fTesselFact = 1.0f;           // object tessalation
int UpdateFlags = 0;                // extra data sent to update function
int Type = 0;                       // screen saver index (into function arrays)
TCHAR achTexPathname[MAX_PATH];    // current texture pathname
static int nTexFileOffset;          // current filename offset into pathname

// Velocity of the OpenGL window that floats around.

static float xTransInc = 2.0f;
static float yTransInc = 2.5f;

// Lighting properties.

static const RGBA lightAmbient   = {0.21f, 0.21f, 0.21f, 1.0f};
static const RGBA light0Ambient  = {0.0f, 0.0f, 0.0f, 1.0f};
static const RGBA light0Diffuse  = {0.7f, 0.7f, 0.7f, 1.0f};
static const RGBA light0Specular = {1.0f, 1.0f, 1.0f, 1.0f};
static const GLfloat light0Pos[]      = {100.0f, 100.0f, 100.0f, 0.0f};

// Material properties.

static RGBA matlColors[7] = {{1.0f, 0.0f, 0.0f, 1.0f},
                             {0.0f, 1.0f, 0.0f, 1.0f},
                             {0.0f, 0.0f, 1.0f, 1.0f},
                             {1.0f, 1.0f, 0.0f, 1.0f},
                             {0.0f, 1.0f, 1.0f, 1.0f},
                             {1.0f, 0.0f, 1.0f, 1.0f},
                             {0.235f, 0.0f, 0.78f, 1.0f},
                            };

extern HPALETTE Hpal;

extern BOOL fChildPreview;  // from scrnsave.lib

LONG WndProc(HWND, UINT, WPARAM, LPARAM);

static int xSize;
static int ySize;
static int wxSize;
static int wySize;
static int updates = 0;

extern void updateStripScene(HWND, int);
extern void updateDropScene(HWND, int);
extern void updateLemScene(HWND, int);
extern void updateExplodeScene(HWND, int);
extern void updateWinScene(HWND, int);
extern void updateTexScene(HWND, int);
extern void initStripScene(void);
extern void initDropScene(void);
extern void initLemScene(void);
extern void initExplodeScene(void);
extern void initWinScene(void);
extern void initTexScene(void);
extern void delStripScene(void);
extern void delDropScene(void);
extern void delLemScene(void);
extern void delExplodeScene(void);
extern void delWinScene(void);
extern void delTexScene(void);

typedef void (*PTRUPDATE)(HWND, int);
typedef void (*ptrdel)();
typedef void (*ptrinit)();

void TimerProc(HWND);
BOOL fOnWin95( void );

// Each screen saver style puts its hook functions into the function
// arrays below.  A consistent ordering of the functions is required.

static PTRUPDATE updateFuncs[] =
    {updateWinScene, updateExplodeScene,updateStripScene, updateStripScene,
     updateDropScene, updateLemScene, updateTexScene};
static ptrdel delFuncs[] =
    {delWinScene, delExplodeScene, delStripScene, delStripScene,
     delDropScene, delLemScene, delTexScene};
static ptrinit initFuncs[] =
    {initWinScene, initExplodeScene, initStripScene, initStripScene,
     initDropScene, initLemScene, initTexScene};
static int idsStyles[] =
    {IDS_LOGO, IDS_EXPLODE, IDS_RIBBON, IDS_2RIBBON,
     IDS_SPLASH, IDS_TWIST, IDS_FLAG};

#define MAX_TYPE    ( sizeof(initFuncs) / sizeof(ptrinit) - 1 )

// Each screen saver style can choose which dialog box controls it wants
// to use.  These flags enable each of the controls.  Controls not choosen
// will be disabled.

#define OPT_COLOR_CYCLE     0x00000001
#define OPT_SMOOTH_SHADE    0x00000002
#define OPT_TESSEL          0x00000008
#define OPT_SIZE            0x00000010
#define OPT_TEXTURE         0x00000020
#define OPT_STD             ( OPT_COLOR_CYCLE | OPT_SMOOTH_SHADE | OPT_TESSEL | OPT_SIZE )

static ULONG gflConfigOpt[] = {
     OPT_STD,               // Windows logo
     OPT_STD,               // Explode
     OPT_STD,               // Strip
     OPT_STD,               // Strip
     OPT_STD,               // Drop
     OPT_STD,               // Twist (lemniscate)
     OPT_SMOOTH_SHADE | OPT_TESSEL | OPT_SIZE | OPT_TEXTURE  // Texture mapped flag
};

// Maximum texture bitmap dimensions.

#define TEX_WIDTH_MAX   512
#define TEX_HEIGHT_MAX  512

#ifdef MEMDEBUG
void xprintf(char *str, ...)
{
    va_list ap;
    char buffer[256];

    va_start(ap, str);
    vsprintf(buffer, str, ap);

    OutputDebugString(buffer);
    va_end(ap);
}
#endif

void *SaverAlloc(ULONG size)
{
    void *mPtr;

    mPtr = malloc(size);
#ifdef MEMDEBUG
    totalMem += size;
    xprintf("malloc'd %x, size %d\n", mPtr, size);
#endif
    return mPtr;
}

void SaverFree(void *pMem)
{
#ifdef MEMDEBUG
    totalMem -= _msize(pMem);
    xprintf("free %x, size = %d, total = %d\n", pMem, _msize(pMem), totalMem);
#endif
    free(pMem);
}

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static void getIniSettings()
{
    int   options;
    int   optMask = 1;
    TCHAR sectName[30];
    TCHAR itemName[30];
    TCHAR fname[30];
    HKEY  hkey;
    TCHAR achDefaultBitmap[MAX_PATH];
    LONG  cjDefaultBitmap = MAX_PATH;
    int   tessel;

    LoadString(hMainInstance, IDS_GENNAME, szScreenSaver, sizeof(szScreenSaver) / sizeof(TCHAR));
    LoadString(hMainInstance, IDS_SCREENSAVERTITLE, szScreenSaverTitle, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_TEXTUREFILTER, szTextureFilter, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_TEXTUREDIALOGTITLE, szTextureDialogTitle, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_BMP, szBmp, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_DOTBMP, szDotBmp, GEN_STRING_SIZE);

    if (LoadString(hMainInstance, IDS_SAVERNAME, sectName, 30) &&
        LoadString(hMainInstance, IDS_INIFILE, fname, 30))
    {
        if (LoadString(hMainInstance, IDS_OPTIONS, itemName, 30))
            options = GetPrivateProfileInt(sectName, itemName,
                                            -1, fname);
        if (options >= 0)
        {
            bSmoothShading = ((options & optMask) != 0);
            optMask <<= 1;
            bColorCycle = ((options & optMask) != 0);
            UpdateFlags = (bColorCycle << 1);
        }

        if (LoadString(hMainInstance, IDS_OBJTYPE, itemName, 30))
            Type = GetPrivateProfileInt(sectName, itemName,
                                         0, fname);

        // Sanity check Type.  Don't want to index into function arrays
        // with a bad index!

        Type = min(Type, MAX_TYPE);

        // Set flag so that updateStripScene will do two strips instead
        // of one.

        if (Type == 3)
            UpdateFlags |= 0x4;

        if (LoadString(hMainInstance, IDS_TESSELATION, itemName, 30))
            tessel = GetPrivateProfileInt(sectName, itemName,
                                           100, fname);
        if (tessel <= 100)
            fTesselFact  = (float)tessel / 100.0f;
        else
            fTesselFact = 1.0f + (((float)tessel - 100.0f) / 100.0f);

        if (LoadString(hMainInstance, IDS_SIZE, itemName, 30))
            uSize = GetPrivateProfileInt(sectName, itemName,
                                          50, fname);

        if( fOnWin95() )
            lstrcpy(achDefaultBitmap, TEXT("setup.bmp"));
        else {
            
            if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                           TEXT("System\\CurrentControlSet\\Control\\ProductOptions"),
                           0,
                           KEY_QUERY_VALUE,
                           &hkey) == ERROR_SUCCESS )
            {
                if ( RegQueryValueEx(hkey,
                                  TEXT("ProductType"),
                                  (LPDWORD) NULL,
                                  (LPDWORD) NULL,
                                  (LPBYTE) achDefaultBitmap,
                                  (LPDWORD) &cjDefaultBitmap) == ERROR_SUCCESS
                     && (cjDefaultBitmap / sizeof(TCHAR) + 4) <= MAX_PATH )
                    lstrcat(achDefaultBitmap, szDotBmp);
                else
                    lstrcpy(achDefaultBitmap, TEXT("winnt.bmp"));

                RegCloseKey(hkey);
            }
            else
                lstrcpy(achDefaultBitmap, TEXT("winnt.bmp"));
        }
    
        if (LoadString(hMainInstance, IDS_TEXTURE, itemName, 30))
            GetPrivateProfileString(sectName, itemName, achDefaultBitmap,
                                         achTexPathname, MAX_PATH, fname);

        if (LoadString(hMainInstance, IDS_TEXTURE_FILE_OFFSET, itemName, 30))
            nTexFileOffset = GetPrivateProfileInt(sectName, itemName,
                                                       0, fname);
    }
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static void saveIniSettings(HWND hDlg)
{
    TCHAR sectName[30];
    TCHAR itemName[30];
    TCHAR fname[30];
    TCHAR tmp[30];
    int pos, options;
    int optMask = 1;

    bSmoothShading = IsDlgButtonChecked(hDlg, DLG_SETUP_SMOOTH);
    bColorCycle = IsDlgButtonChecked(hDlg, DLG_SETUP_CYCLE);
    Type = SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_GETCURSEL,
                              0, 0);

    pos = GetScrollPos(GetDlgItem(hDlg,DLG_SETUP_TESSEL),SB_CTL);

    if (pos <= 100)
        fTesselFact  = (float)pos / 100.0f;
    else
        fTesselFact = 1.0f + (((float)pos - 100.0f) / 100.0f);

    uSize = GetScrollPos(GetDlgItem(hDlg, DLG_SETUP_SIZE),SB_CTL);


    if (LoadString(hMainInstance, IDS_SAVERNAME, sectName, 30) &&
        LoadString(hMainInstance, IDS_INIFILE, fname, 30)) {
        if (LoadString(hMainInstance, IDS_OPTIONS, itemName, 30)) {
            options = bColorCycle;
            options <<= 1;
            options |= bSmoothShading;

            wsprintf(tmp, TEXT("%ld"), options);
            WritePrivateProfileString(sectName, itemName,
                                      tmp, fname);
        }
        if (LoadString(hMainInstance, IDS_OBJTYPE, itemName, 30)) {
            wsprintf(tmp, TEXT("%ld"), Type);
            WritePrivateProfileString(sectName, itemName,
                                      tmp, fname);
        }
        if (LoadString(hMainInstance, IDS_TESSELATION, itemName, 30)) {
            wsprintf(tmp, TEXT("%ld"), pos);
            WritePrivateProfileString(sectName, itemName,
                                      tmp, fname);
        }
        if (LoadString(hMainInstance, IDS_SIZE, itemName, 30)) {
            wsprintf(tmp, TEXT("%ld"), uSize);
            WritePrivateProfileString(sectName, itemName,
                                      tmp, fname);
        }
        if (LoadString(hMainInstance, IDS_TEXTURE, itemName, 30)) {
            WritePrivateProfileString(sectName, itemName,
                                      achTexPathname, fname);
        }
        if (LoadString(hMainInstance, IDS_TEXTURE_FILE_OFFSET, itemName, 30)) {
            wsprintf(tmp, TEXT("%ld"), nTexFileOffset);
            WritePrivateProfileString(sectName, itemName,
                                      tmp, fname);
        }
    }
}

/******************************Public*Routine******************************\
* setupDialogControls
*
* Setup the dialog controls based on the current global state.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static void setupDialogControls(HWND hDlg)
{
    int pos;

    CheckDlgButton(hDlg, DLG_SETUP_SMOOTH, bSmoothShading);
    CheckDlgButton(hDlg, DLG_SETUP_CYCLE, bColorCycle);

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_SMOOTH),
                 gflConfigOpt[Type] & OPT_SMOOTH_SHADE );
    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_CYCLE),
                 gflConfigOpt[Type] & OPT_COLOR_CYCLE );

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TESSEL),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MIN),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MAX),
                 gflConfigOpt[Type] & OPT_TESSEL);

    if ( gflConfigOpt[Type] & OPT_TESSEL )
    {
        SetScrollRange(GetDlgItem(hDlg, DLG_SETUP_TESSEL), SB_CTL, 0, 200,
                       FALSE);
        if (fTesselFact <= 1.0f)
            pos = (int)(fTesselFact * 100.0f);
        else
            pos = 100 + (int) ((fTesselFact - 1.0f) * 100.0f);

        SetScrollPos(GetDlgItem(hDlg, DLG_SETUP_TESSEL), SB_CTL, pos, TRUE);
    }

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_SIZE),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE_MIN),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE_MAX),
                 gflConfigOpt[Type] & OPT_SIZE);

    if ( gflConfigOpt[Type] & OPT_SIZE )
    {
        SetScrollRange(GetDlgItem(hDlg, DLG_SETUP_SIZE), SB_CTL, 0, 100,
                       FALSE);
        SetScrollPos(GetDlgItem(hDlg, DLG_SETUP_SIZE), SB_CTL, uSize, TRUE);
    }

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TEXTURE),
                 gflConfigOpt[Type] & OPT_TEXTURE);
}

/******************************Public*Routine******************************\
* getTextureBitmap
*
* Use the common dialog GetOpenFileName to get the name of a bitmap file
* for use as a texture.  This function will not return until the user
* either selects a valid bitmap or cancels.  If a valid bitmap is selected
* by the user, the global array achTexPathname will have the full path
* to the bitmap file and the global value nTexFileOffset will have the
* offset from the beginning of achTexPathname to the pathless file name.
*
* If the user cancels, achTexPathname and nTexFileOffset will remain
* unchanged.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

void getTextureBitmap(HWND hDlg)
{
    OPENFILENAME ofn;
    TCHAR dirName[MAX_PATH];
    TCHAR fileName[MAX_PATH];
    PTSTR psz;
    BOOL bTryAgain;

// Make dialog look nice by parsing out the initial path and
// file name from the full pathname.  If this isn't done, then
// dialog has a long ugly name in the file combo box and
// directory will end up with the default current directory.

    if (nTexFileOffset) {
        lstrcpy(dirName, achTexPathname);
        dirName[nTexFileOffset-1] = TEXT('\0');
        lstrcpy(fileName, &achTexPathname[nTexFileOffset]);
    }
    else {

    // If nTexFileOffset is zero, then achTexPathname is not a full path.
    // Attempt to make it a full path by calling SearchPathW.

        if ( SearchPath(NULL, dirName, NULL, MAX_PATH,
                         achTexPathname, &psz) )
        {
            nTexFileOffset = psz - achTexPathname;
            dirName[nTexFileOffset-1] = TEXT('\0');
            lstrcpy(fileName, &dirName[nTexFileOffset]);
        }

    // Give up and use the Windows system directory.

        else
        {
            GetWindowsDirectory(dirName, MAX_PATH);
            lstrcpy(fileName, achTexPathname);
        }
    }

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hDlg;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = szTextureFilter;
    ofn.lpstrCustomFilter = (LPTSTR) NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = fileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFileTitle = (LPTSTR) NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = dirName;
    ofn.lpstrTitle = szTextureDialogTitle;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = szBmp;
    ofn.lCustData = 0;
    ofn.lpfnHook = (LPOFNHOOKPROC) NULL;
    ofn.lpTemplateName = (LPCTSTR) NULL;

    do {
    // Invoke the common file dialog.  If it succeeds, then validate
    // the bitmap file.  If not valid, make user try again until either
    // they pick a good one or cancel the dialog.

        bTryAgain = FALSE;

        if ( GetOpenFileName(&ofn) ) {
            ULONG w, h;

            if (bVerifyDIB(fileName, &w, &h)) {
                if ( w <= TEX_WIDTH_MAX && h <= TEX_HEIGHT_MAX )
                {
                    lstrcpy(achTexPathname, fileName);
                    nTexFileOffset = ofn.nFileOffset;
                }
                else {
                    if ( LoadString(hMainInstance, IDS_WARNING, gachTitle, MAX_PATH) &&
                         LoadString(hMainInstance, IDS_BITMAP_SIZE, gachFormat, MAX_PATH) )
                    {
                        wsprintf(gach, gachFormat, TEX_WIDTH_MAX, TEX_HEIGHT_MAX);
                        MessageBox(NULL, gach, gachTitle, MB_OK);
                    }
                    bTryAgain = TRUE;
                }
            }
            else {
                if ( LoadString(hMainInstance, IDS_WARNING, gachTitle, MAX_PATH) &&
                     LoadString(hMainInstance, IDS_BITMAP_INVALID, gachFormat, MAX_PATH) )
                {
                    MessageBox(NULL, gachFormat, gachTitle, MB_OK);
                }
                bTryAgain = TRUE;
            }
        }

    // If need to try again, recompute dir and file name so dialog
    // still looks nice.

        if (bTryAgain && ofn.nFileOffset) {
            lstrcpy(dirName, fileName);
            dirName[ofn.nFileOffset-1] = TEXT('\0');
            lstrcpy(fileName, &fileName[ofn.nFileOffset]);
        }

    } while (bTryAgain);
}

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

/******************************Public*Routine******************************\
* ScreenSaverConfigureDialog
*
* Processes messages for the configuration dialog box.
*
\**************************************************************************/

BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{
    int wTmp;
    TCHAR ach[GEN_STRING_SIZE];

    switch (message) {
        case WM_INITDIALOG:
            getIniSettings();
            setupDialogControls(hDlg);

            for (wTmp = 0; wTmp <= MAX_TYPE; wTmp++) {
                LoadString(hMainInstance, idsStyles[wTmp], ach, GEN_STRING_SIZE);
                SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_ADDSTRING, 0,
                                   (LPARAM) ach);
            }
            SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_SETCURSEL, Type, 0);

            return TRUE;

        case WM_HSCROLL:
            if (lParam == (LPARAM) GetDlgItem(hDlg,DLG_SETUP_TESSEL)) {
                wTmp = GetScrollPos(GetDlgItem(hDlg,DLG_SETUP_TESSEL),SB_CTL);
                switch(LOWORD(wParam))
                {
                    case SB_PAGEDOWN:
                        wTmp += 10;
                    case SB_LINEDOWN:
                        wTmp += 1;
                        wTmp = min(200, wTmp);
                        break;
                    case SB_PAGEUP:
                        wTmp -= 10;
                    case SB_LINEUP:
                        wTmp -= 1;
                        wTmp = max(0, (int)wTmp);
                        break;
                    case SB_THUMBPOSITION:
                        wTmp = HIWORD(wParam);
                        break;
                    default:
                        break;
                }
                SetScrollPos(GetDlgItem(hDlg, DLG_SETUP_TESSEL), SB_CTL, wTmp,
                             TRUE);
            } else {
                wTmp = GetScrollPos(GetDlgItem(hDlg,DLG_SETUP_SIZE),SB_CTL);
                switch(LOWORD(wParam))
                {
                    case SB_PAGEDOWN:
                        wTmp += 10;
                    case SB_LINEDOWN:
                        wTmp += 1;
                        wTmp = min(200, wTmp);
                        break;
                    case SB_PAGEUP:
                        wTmp -= 10;
                    case SB_LINEUP:
                        wTmp -= 1;
                        wTmp = max(0, (int)wTmp);
                        break;
                    case SB_THUMBPOSITION:
                        wTmp = HIWORD(wParam);
                        break;
                    default:
                        break;
                }
                SetScrollPos(GetDlgItem(hDlg, DLG_SETUP_SIZE), SB_CTL, wTmp,
                             TRUE);
            }
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DLG_SETUP_TYPES:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                            Type = SendDlgItemMessage(hDlg, DLG_SETUP_TYPES,
                                                      CB_GETCURSEL, 0, 0);
                            setupDialogControls(hDlg);
                            break;
                        default:
                            break;
                    }
                    return FALSE;

                case DLG_SETUP_TEXTURE:
                    getTextureBitmap(hDlg);
                    break;

                case IDOK:
                    saveIniSettings(hDlg);
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;

                case DLG_SETUP_SMOOTH:
                case DLG_SETUP_CYCLE:
                default:
                    break;
            }
            return TRUE;
            break;

        default:
            return 0;
    }
    return 0;
}


/******************************Public*Routine******************************\
* initClass
*
* Register the class for the OpenGL window.  The OpenGL window floats on
* top of the screen saver window, bouncing off each of the screen edges.
*
\**************************************************************************/

BOOL initClass(HANDLE hMainInstance)
{
    WNDCLASS class;

    class.style = CS_VREDRAW | CS_HREDRAW;
    class.lpfnWndProc = (WNDPROC)WndProc;
    class.cbClsExtra = 0;
    class.cbWndExtra = 0;
    class.hInstance = hMainInstance;
    class.hIcon = NULL;
    class.hCursor = NULL;
    class.hbrBackground = NULL;
    class.lpszMenuName = (LPCTSTR)NULL;
    class.lpszClassName = (LPCTSTR)szClassName;
    return RegisterClass(&class);
}

/******************************Public*Routine******************************\
* initInstance
*
* Create the OpenGL window.  The OpenGL window floats on top of the screen
* saver window, bouncing off each of the screen edges.
*
* History
*  Aug. 7, 95 :
*    - Workaround for win95 window initialization bug: When floater window
*      created on top of main window, some initialization state gets
*      corrupted, as seen by the floater leaving a rectangle of bad
*      data where it was created, causing update artifacts whenever it
*      traverses this zone.  Until this is fixed, create the window
*      *offscreen*.
*
\**************************************************************************/

BOOL initInstance(HANDLE hMainInstance, int nCmdShow, HWND hwndParent)
{
    HDC hdc;
    DWORD style;
    float sizeFact;
    float sizeScale;

    hdc = GetDC(NULL);

    xSize = GetDeviceCaps(hdc, HORZRES);
    ySize = GetDeviceCaps(hdc, VERTRES);

    sizeScale = (float)uSize / 100.0f;

    sizeFact = 0.25f + (0.25f * sizeScale);
    xTransInc = 2.0f + (4.0f * sizeScale);
    yTransInc = 2.0f + (4.5f * sizeScale);
    wxSize = wySize = (int)((float)ySize * sizeFact);

    ReleaseDC(NULL, hdc);

    style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

    hwndOpenGL = CreateWindowEx(WS_EX_TOPMOST,
                                 szClassName,
                                 szScreenSaverTitle,
                                 style,
                                 -xSize,             // horizontal pos
                                 -ySize,             // vertical pos
                                 wxSize,             // width
                                 wySize,             // height
                                 hwndParent,         // parent
                                 NULL,               // menu
                                 hMainInstance,
                                 NULL
                                );

    if (!hwndOpenGL)
        return FALSE;

    ShowWindow(hwndOpenGL, SW_SHOW);

    UpdateWindow(hwndOpenGL);

    return TRUE;
}

/******************************Public*Routine******************************\
* CreateRGBPalette
*
* If required, create an RGB palette.  If 8BPP, also take over the static
* colors so OpenGL will have a one-to-one mapping to the physical palette.
*
\**************************************************************************/

static unsigned char threeto8[8] = {
    0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};

static unsigned char twoto8[4] = {
    0, 0x55, 0xaa, 0xff
};

static unsigned char oneto8[2] = {
    0, 255
};

static unsigned char
ComponentFromIndex(i, nbits, shift)
{
    unsigned char val;

    val = i >> shift;
    switch (nbits) {

    case 1:
        val &= 0x1;
        return oneto8[val];

    case 2:
        val &= 0x3;
        return twoto8[val];

    case 3:
        val &= 0x7;
        return threeto8[val];

    default:
        return 0;
    }
}


static void
CreateRGBPalette(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd, *ppfd;
    LOGPALETTE *pPal;
    int n, i;

    ppfd = &pfd;
    n = GetPixelFormat(hdc);
    DescribePixelFormat(hdc, n, sizeof(PIXELFORMATDESCRIPTOR), ppfd);

    if (ppfd->dwFlags & PFD_NEED_PALETTE) {
        n = 1 << ppfd->cColorBits;
        pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                n * sizeof(PALETTEENTRY));
        pPal->palVersion = 0x300;
        pPal->palNumEntries = n;
        for (i=0; i < n; i++) {
            pPal->palPalEntry[i].peRed =
                    ComponentFromIndex(i, ppfd->cRedBits, ppfd->cRedShift);
            pPal->palPalEntry[i].peGreen =
                    ComponentFromIndex(i, ppfd->cGreenBits, ppfd->cGreenShift);
            pPal->palPalEntry[i].peBlue =
                    ComponentFromIndex(i, ppfd->cBlueBits, ppfd->cBlueShift);
            pPal->palPalEntry[i].peFlags = ((n == 256) && (i == 0 || i == 255))
                                           ? 0 : PC_NOCOLLAPSE;
        }

        ghPalette = CreatePalette(pPal);
        LocalFree(pPal);

        if ( n == 256 )
        {
            gbUseStatic = TRUE;
            guiOldStaticUse = SetSystemPaletteUse(hdc, SYSPAL_NOSTATIC);
        }

        ghpalOld = SelectPalette(hdc, ghPalette, FALSE);
        n = RealizePalette(hdc);
    }
}

/******************************Public*Routine******************************\
* bSetupPixelFormat
*
* Select and set the pixel format for the DC.
*
\**************************************************************************/

BOOL bSetupPixelFormat(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd, *ppfd;
    int pixelformat;

    ppfd = &pfd;

    ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR);
    ppfd->nVersion = 1;
    ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;

    ppfd->iLayerType = PFD_MAIN_PLANE;
    ppfd->cAlphaBits = 0;
    ppfd->cAuxBuffers = 0;
    ppfd->bReserved = 0;

    ppfd->iPixelType = PFD_TYPE_RGBA;
    ppfd->cColorBits = 24;

    ppfd->cDepthBits = 16;

    ppfd->cAccumBits = 0;
    ppfd->cStencilBits = 0;

    if ( (pixelformat = ChoosePixelFormat(hdc, ppfd)) == 0  ||
         SetPixelFormat(hdc, pixelformat, ppfd) == FALSE )
        return FALSE;

    return TRUE;
}

/******************************Public*Routine******************************\
* initMaterial
*
* Initialize the material properties.
*
\**************************************************************************/

void initMaterial(int id, float r, float g, float b, float a)
{
    Material[id].ka.r = r;
    Material[id].ka.g = g;
    Material[id].ka.b = b;
    Material[id].ka.a = a;

    Material[id].kd.r = r;
    Material[id].kd.g = g;
    Material[id].kd.b = b;
    Material[id].kd.a = a;

    Material[id].ks.r = 1.0f;
    Material[id].ks.g = 1.0f;
    Material[id].ks.b = 1.0f;
    Material[id].ks.a = 1.0f;

    Material[id].specExp = 128.0f;
    Material[id].indexStart = (float) (id * PALETTE_PER_MATL);
}

/******************************Public*Routine******************************\
* hrcInitGL
*
* Setup OpenGL.
*
\**************************************************************************/

HGLRC hrcInitGL(HWND hwnd, HDC hdc)
{
    HGLRC hrc;
    RECT rect;

// Save the window size.

    GetClientRect(hwnd, &rect);
    wxSize = rect.right - rect.left;
    wySize = rect.bottom - rect.top;

// Setup the DC and create an OpenGL rendering context.

    bSetupPixelFormat(hdc);
    CreateRGBPalette(hdc);
    hrc = wglCreateContext(hdc);
    if (!hrc || !wglMakeCurrent(hdc, hrc))
        return NULL;

// Set the OpenGL clear color to black.

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

// Enable the z-buffer.

    glEnable(GL_DEPTH_TEST);

// Select the shading model.

    if (bSmoothShading)
        glShadeModel(GL_SMOOTH);
    else
        glShadeModel(GL_FLAT);

// Setup the OpenGL matrices.

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

// Setup the lighting.

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (GLfloat *) &lightAmbient);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
    glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat *) &light0Ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat *) &light0Diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, (GLfloat *) &light0Specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

// Setup the material properties.

    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &Material[0].ks);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, (GLfloat *) &Material[0].specExp);

// Setup the OpenGL viewport.

    glViewport(0, 0, wxSize, wySize);

// Return the OpenGL rendering context handle.

    return hrc;
}


/******************************Public*Routine******************************\
* vCleanupGL
*
* Cleanup objects allocated, etc.
*
\**************************************************************************/

void
vCleanupGL(HGLRC hrc, HWND hwnd)
{
    HDC hdc = wglGetCurrentDC();

    wglMakeCurrent(NULL, NULL);

    if (ghPalette)
    {
        if ( gbUseStatic )
        {
            SetSystemPaletteUse(hdc, guiOldStaticUse);
            PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);
        }
        DeleteObject(SelectPalette(hdc, ghpalOld, FALSE));
    }
    /*  Release the dc */
    ReleaseDC(hwnd, hdc);

    /*  Destroy our context */
    wglDeleteContext(hrc);
}


/******************************Public*Routine******************************\
* vMoveBuffer
*
* Move the OpenGL window.
*
\**************************************************************************/

void vMoveBuffer(int curX, int curY,
                int lastX, int lastY,
                int pxsize, int pysize)
{
    SetWindowPos(hwndOpenGL, HWND_TOPMOST, curX, curY, 0, 0, SWP_NOSIZE |
                 SWP_NOCOPYBITS);
}


/******************************Public*Routine******************************\
* vShowBuffer
*
* This is the function that moves the OpenGL window around, causing it to
* bounce around.  Each time the window is moved, the contents of the
* window are updated from the hidden (or back) buffer by SwapBuffers().
*
\**************************************************************************/

BOOL vShowBuffer(HWND hwnd)
{
    HDC hdc;
    static float xtrans = 0.0f;
    static float ytrans = 0.0f;
    static int lastXt = 0;
    static int lastYt = 0;
    static int bInited = FALSE;
    static int interBounce = 0;
    int bounce = FALSE;
    int xt, yt;

    if (!bInited) {
        bInited = TRUE;

        srand(clock() & 0xffff);
        rand();
        xtrans = ((float)rand() / (float)RAND_MAX) * (float)(xSize - wxSize);
        ytrans = ((float)rand() / (float)RAND_MAX) * (float)(ySize - wySize);
        lastXt = (int) xtrans;
        lastYt = (int) ytrans;
    }

// Move the window to its next position.

    xt = (int)xtrans;
    yt = (int)ytrans;

    vMoveBuffer(xt, yt, lastXt, lastYt, wxSize, wySize);

// Compute the next window position.

    lastXt = xt;
    lastYt = yt;

    xtrans += xTransInc;
    ytrans += yTransInc;

    if ((xtrans + wxSize) > xSize) {
        xtrans = (float) (xSize - wxSize);
        xTransInc = -xTransInc;
        bounce = TRUE;
    } else if (xtrans < 0.0f) {
        xtrans = 0.0f;
        xTransInc = -xTransInc;
        bounce = TRUE;
    }

    if ((ytrans + wySize) > ySize) {
        ytrans = (float) (ySize - wySize);
        yTransInc = -yTransInc;
        bounce = TRUE;
    } else if (ytrans < 0.0f) {
        ytrans = 0.0f;
        yTransInc = -yTransInc;
        bounce = TRUE;
    }

// Synchronize with OpenGL.  Flush the OpenGL commands and wait for completion.

    glFinish();

// Do a SwapBuffers to update the window contents with the latest rendering
// just completed by OpenGL.

    hdc = wglGetCurrentDC();
    SwapBuffers(hdc);

    interBounce++;

    if (bounce) {
        if (interBounce < 10)
            bounce = FALSE;
        else
            interBounce = 0;
    }

    return bounce;
}


/******************************Public*Routine******************************\
* HsvToRgb
*
* HSV to RGB color space conversion.  From pg. 593 of Foley & van Dam.
*
\**************************************************************************/

void HsvToRgb(float h, float s, float v, float *r, float *g, float *b)
{
    float i, f, p, q, t;

    if (s == 0.0f)     // assume h is undefined
        *r = *g = *b = v;
    else {
        if (h >= 360.0f)
            h = 0.0f;
        h = h / 60.0f;
        i = (float) floor(h);
        f = h - i;
        p = v * (1.0f - s);
        q = v * (1.0f - (s * f));
        t = v * (1.0f - (s * (1.0f - f)));
        switch ((int)i) {
        case 0:
            *r = v;
            *g = t;
            *b = p;
            break;
        case 1:
            *r = q;
            *g = v;
            *b = p;
            break;
        case 2:
            *r = p;
            *g = v;
            *b = t;
            break;
        case 3:
            *r = p;
            *g = q;
            *b = v;
            break;
        case 4:
            *r = t;
            *g = p;
            *b = v;
            break;
        case 5:
            *r = v;
            *g = p;
            *b = q;
            break;
        default:
            break;
        }
    }
}


/******************************Public*Routine******************************\
* TimerProc
*
* Every time a timer event fires off, update the scene.  The scene is
* rendered to the back buffer, so SwapBuffers must be called before
* the new scene will be visible.
*
\**************************************************************************/

void TimerProc(HWND hwnd)
{
    static int busy = FALSE;

    if (busy)
        return;
    busy = TRUE;

    updates++;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    (*updateSceneFunc)(hwnd, UpdateFlags);

    busy = FALSE;
}


/******************************Public*Routine******************************\
* WndProc
*
* Processes messages for the floating OpenGL window.
*
\**************************************************************************/

LONG WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    int i;

    switch (message)
    {
        case WM_CREATE:
            for (i = 0; i < 7; i++)
                initMaterial(i, matlColors[i].r, matlColors[i].g,
                             matlColors[i].b, matlColors[i].a);

            if (hdc = GetDC(hwnd)) {
                if (gHrc = hrcInitGL(hwnd, hdc)) {
                    (*initFuncs[Type])();
                    updateSceneFunc = updateFuncs[Type];
                    /* hdc will be released in vCleanupGL */
                } else {
                    ReleaseDC(hwnd,hdc);
                    return -1;
                }
            }
            break;

        case WM_DESTROY:
            if (gHrc) {
                vCleanupGL(gHrc, hwnd);
                (*delFuncs[Type])();
            }
            PostQuitMessage(0);
            break;

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

        case WM_SYSCOMMAND:
        case WM_SETCURSOR:
        case WM_NCACTIVATE:
        case WM_ACTIVATE:
        case WM_ACTIVATEAPP:
        case WM_MOUSEMOVE:
        case WM_LBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
            return DefScreenSaverProc(hMainWindow, message, wParam, lParam);

        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}


/******************************Public*Routine******************************\
* ScreenSaverProc
*
* Processes messages for the screen saver window.
*
\**************************************************************************/

LONG ScreenSaverProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static BOOL bInited = FALSE;

    switch (message)
    {
        case WM_CREATE:
            getIniSettings();

        // Make sure the selected texture file is OK.

            if ( gflConfigOpt[Type] & OPT_TEXTURE )
            {
                ULONG w, h;
                TCHAR fileName[MAX_PATH];
                PTSTR psz;

                lstrcpy(fileName, achTexPathname);

                if ( SearchPath(NULL, fileName, NULL, MAX_PATH,
                                 achTexPathname, &psz)
                     && bVerifyDIB(achTexPathname, &w, &h)
                     && w <= TEX_WIDTH_MAX && h <= TEX_HEIGHT_MAX
                   )
                {
                    nTexFileOffset = psz - achTexPathname;
                }
                else
                {
                    if( !fOnWin95() && !fChildPreview &&
                         LoadString(hMainInstance, IDS_WARNING, gachTitle, MAX_PATH) &&
                         LoadString(hMainInstance, IDS_RUN_CONTROL_PANEL, gachFormat, MAX_PATH) )
                    {
                        wsprintf(gach, gachFormat, achTexPathname);
                        MessageBox(NULL, gach, gachTitle, MB_OK);
                    }
                }
            }
            break;

        case WM_DESTROY:
            if (idTimer) {
                KillTimer(hwnd, idTimer);
                idTimer = 0;
            }

            if( hwndOpenGL )
                DestroyWindow(hwndOpenGL);
            PostQuitMessage(0);
            break;

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

        // Do initialization of the bouncing top most window here.

            if (!bInited) {
                bInited = TRUE;

                if ( !initClass(hMainInstance) ||
                     !initInstance(hMainInstance, SW_SHOW, hwnd) )
                {

                    if ( LoadString(hMainInstance, IDS_ERROR, gachTitle, MAX_PATH) &&
                         LoadString(hMainInstance, IDS_START_FAILED, gachFormat, MAX_PATH) )
                    {
                        MessageBox(NULL, gachFormat, gachTitle, MB_OK);
                    }
                    return FALSE;
                }

                idTimer = 1;
                SetTimer(hwnd, idTimer, 16, 0);

                SetWindowPos(hwndOpenGL, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
            }

            EndPaint(hwnd, &ps);

            break;

        case WM_TIMER:
            TimerProc(hwndOpenGL);

            break;

        default:
            return DefScreenSaverProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

BOOL fOnWin95( void )
{
    OSVERSIONINFO osvi;

    osvi.dwOSVersionInfoSize = sizeof(osvi);
    return (GetVersionEx(&osvi) &&
                (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) );
}
