/* indexmat.c */

/*
 * Example/test of the GL_SGI_index_material extension
 *
 * This extension adds the GL_INDEX_OFFSET and GL_INDEX_SHIFT
 * material parameters for CI mode lighting.  With this feature
 * we can combine fog and lighting (for example) in color index
 * mode.
 *
 * Also, we can use glEnable(GL_INDEX_MATERIAL_SGI) and
 * glIndexMaterialSGI() to modify the material's GL_INDEX_OFFSET
 * with the glIndex() command.  This is like glColorMaterial
 * in RGB mode.
 *
 * This demo shows two spinning dodecahedrons using two materials
 * selected via glIndex().
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <windows.h>
#include "GL/gl.h"

/* Have to access glIndexMaterialSGI through a pointer, ugh! */
static PFNGLINDEXMATERIALSGIPROC glIndexMaterialSGIptr;

char *className = "OpenGL";
char *windowName = "Index Material";
int winX = 0, winY = 0;
int winWidth = 300, winHeight = 300;

HDC hDC;
HGLRC hGLRC;
HPALETTE hPalette;

void (*idleFunc)(void);

/* Struct used to manage color ramps */
struct colorIndexState {
    GLfloat amb[3];	/* ambient color / bottom of ramp */
    GLfloat diff[3];	/* diffuse color / middle of ramp */
    GLfloat spec[3];	/* specular color / top of ramp */
    GLfloat ratio;	/* ratio of diffuse to specular in ramp */
    GLint indexes[3];	/* where ramp was placed in palette */
};

/*
** Each entry in this array corresponds to a color ramp in the
** palette.  The indexes member of each struct is updated to
** reflect the placement of the color ramp in the palette.
*/
#define NUM_COLORS (sizeof(colors) / sizeof(colors[0]))
struct colorIndexState colors[] = {
    {
        { 0.0F, 0.0F, 0.0F },	/* red */
        { 1.0F, 0.0F, 0.0F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.0F, 0.0F },	/* green */
        { 0.0F, 1.0F, 0.0F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
};

static GLfloat Yrot = 0.0F;

static GLboolean
checkExtension(const char *name)
{
    const char *p = (const char *) glGetString(GL_EXTENSIONS);

    while (p = strstr(p, name)) {
	const char *q = p + strlen(name);

	if (*q == ' ' || *q == '\0') {
	    return GL_TRUE;
	}
	p = q;
    }
    return GL_FALSE;
}

static void
checkError(char *msg)
{
    GLenum err;

    while ((err = glGetError()) != GL_NO_ERROR) {
	printf("Error %d at %s\n", err, msg);
    }
}

static void
init(void)
{
    GLfloat spec[4] = {0.2F, 0.2F, 0.2F, 1.0F};
    GLfloat dif[4]  = {0.8F, 0.8F, 0.8F, 1.0F};
    GLfloat amb[4]  = {0.2F, 0.2F, 0.2F, 1.0F};
    GLint indexes[3];
    GLint i;

    /* Note: indexes are zero based */
    indexes[0] = 0;
    indexes[1] = colors[0].indexes[1] - colors[0].indexes[0];
    indexes[2] = colors[0].indexes[2] - colors[0].indexes[0];

    glMaterialiv( GL_FRONT_AND_BACK, GL_COLOR_INDEXES, indexes );

    glLightfv( GL_LIGHT0, GL_DIFFUSE, dif );
    glLightfv( GL_LIGHT0, GL_AMBIENT, amb );
    glLightfv( GL_LIGHT0, GL_SPECULAR, spec );

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1 );

    /* index material stuff */
    glIndexMaterialSGIptr = (void *) wglGetProcAddress("glIndexMaterialSGI");
    if (!glIndexMaterialSGIptr) {
        printf("Error: renderer doesn't support GL_SGI_index_material\n");
        exit(1);
    }

    /* front and back index offset tracks glIndex(): */
    (*glIndexMaterialSGIptr)( GL_FRONT_AND_BACK, GL_INDEX_OFFSET );
    glEnable( GL_INDEX_MATERIAL_SGI );

    /* test attrib pushing/popping */
    glPushAttrib( GL_LIGHTING_BIT );
    glDisable( GL_INDEX_MATERIAL_SGI );
    (*glIndexMaterialSGIptr)( GL_BACK, GL_INDEX_OFFSET );
    glPopAttrib();

    glGetIntegerv( GL_INDEX_MATERIAL_FACE_SGI, &i );
    assert( i==GL_FRONT_AND_BACK );
    glGetIntegerv( GL_INDEX_MATERIAL_PARAMETER_SGI, &i );
    assert( i==GL_INDEX_OFFSET );
    assert( glIsEnabled(GL_INDEX_MATERIAL_SGI) );

    /* temporary:
    glMateriali( GL_FRONT_AND_BACK, GL_INDEX_SHIFT, -1 );
    glCullFace( GL_FRONT );
    */

    glEnable( GL_DEPTH_TEST );
    checkError("Checkpoint");
}

void
drawDodecahedron(void)
{
#define A (1.61803F)	/* (sqrt(5) + 1) / 2 */
#define B (0.61803F)	/* (sqrt(5) - 1) / 2 */
#define C (1.0F)
    GLfloat vertexes[20][3] = {
	{-A, 0.0F, B }, {-A, 0.0F,-B }, { A, 0.0F,-B }, { A, 0.0F, B },
	{ B,-A, 0.0F }, {-B,-A, 0.0F }, {-B, A, 0.0F }, { B, A, 0.0F },
	{ 0.0F, B,-A }, { 0.0F,-B,-A }, { 0.0F,-B, A }, { 0.0F, B, A },
	{-C,-C, C }, {-C,-C,-C }, { C,-C,-C }, { C,-C, C },
	{-C, C, C }, {-C, C,-C }, { C, C,-C }, { C, C, C },
    };
#undef A
#undef B
#undef C
    GLint polygons[12][5] = {
	{  0, 12, 10, 11, 16 },
	{  1, 17,  8,  9, 13 },
	{  2, 14,  9,  8, 18 },
	{  3, 19, 11, 10, 15 },
	{  4, 14,  2,  3, 15 },
	{  5, 12,  0,  1, 13 },
	{  6, 17,  1,  0, 16 },
	{  7, 19,  3,  2, 18 },
	{  8, 17,  6,  7, 18 },
	{  9, 14,  4,  5, 13 },
	{ 10, 12,  5,  4, 15 },
	{ 11, 19,  7,  6, 16 },
    };
    int i;

    for (i=0; i<12; ++i) {
	GLfloat *p0, *p1, *p2, d;
	GLfloat u[3], v[3], n[3];

	p0 = &vertexes[polygons[i][0]][0];
	p1 = &vertexes[polygons[i][1]][0];
	p2 = &vertexes[polygons[i][2]][0];

	u[0] = p2[0] - p1[0];
	u[1] = p2[1] - p1[1];
	u[2] = p2[2] - p1[2];

	v[0] = p0[0] - p1[0];
	v[1] = p0[1] - p1[1];
	v[2] = p0[2] - p1[2];

	n[0] = u[1]*v[2] - u[2]*v[1];
	n[1] = u[2]*v[0] - u[0]*v[2];
	n[2] = u[0]*v[1] - u[1]*v[0];

	d = 1.0F / (GLfloat) sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
	n[0] *= d;
	n[1] *= d;
	n[2] *= d;

	glBegin(GL_POLYGON);
	glNormal3fv(n);
	glVertex3fv(p0);
	glVertex3fv(p1);
	glVertex3fv(p2);
	glVertex3fv(vertexes[polygons[i][3]]);
	glVertex3fv(vertexes[polygons[i][4]]);
	glEnd();
    }
}

static void
redraw(void)
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glPushMatrix();
    glTranslatef( -2.0F, 0.0F, 0.0F );
    glRotatef( Yrot, 0.0F, 1.0F, 0.0F );
    /* Select the material via glIndex */
    glIndexi( colors[0].indexes[0] );
    /*OR glMateriali( GL_FRONT_AND_BACK, GL_INDEX_OFFSET, colors[0].indexes[0] );*/
    drawDodecahedron();
    glPopMatrix();

    glPushMatrix();
    glTranslatef( 2.0F, 0.0F, 0.0F );
    glRotatef( Yrot, 0.0F, 1.0F, 0.0F );
    /* Select the material via glIndex */
    glIndexi( colors[1].indexes[0] );
    /*OR glMateriali( GL_FRONT_AND_BACK, GL_INDEX_OFFSET, colors[1].indexes[0] );*/
    drawDodecahedron();
    glPopMatrix();

    SwapBuffers(hDC);
}

static void
idleRedraw(void)
{
    Yrot += 2.0f;
    redraw();
}

static void
resize(void)
{
    glViewport( 0, 0, winWidth, winHeight );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glFrustum( -2.0F, 2.0F, -2.0F, 2.0F, 5.0F, 25.0F );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0.0F, 0.0F, -15.0F );
}

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

void
setupPalette(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pfd;
    LOGPALETTE* pPal;
    int pixelFormat = GetPixelFormat(hDC);
    int paletteSize;

    DescribePixelFormat(hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    /*
    ** Determine if a palette is needed and if so what size.
    */
    if (pfd.dwFlags & PFD_NEED_PALETTE ||
		pfd.iPixelType == PFD_TYPE_COLORINDEX) {
	paletteSize = 1 << pfd.cColorBits;
	if (paletteSize > 4096) {
	    paletteSize = 4096;
	}
    } else {
	return;
    }

    pPal = (LOGPALETTE*)
	malloc(sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY));
    pPal->palVersion = 0x300;
    pPal->palNumEntries = paletteSize;

    /*
    ** Fill the logical palette with color ramps.
    **
    ** Set up the logical palette so that it can be realized
    ** into the system palette as an identity palette.
    **
    ** 1) The default static entries should be present and at the right
    **    location.  The easiest way to do this is to grab them from
    **    the current system palette.
    **
    ** 2) All non-static entries should be initialized to unique values.
    **    The easiest way to do this is to ensure that all of the non-static
    **    entries have the PC_NOCOLLAPSE flag bit set.
    */
    {
	int numRamps = NUM_COLORS;
	int rampSize = (paletteSize - 20) / numRamps;
	int extra = (paletteSize - 20) - (numRamps * rampSize);
	int i, r;

	/*
	** Initialize static entries by copying them from the
	** current system palette.
	*/
	GetSystemPaletteEntries(hDC, 0, paletteSize, &pPal->palPalEntry[0]);

	/*
	** Fill in non-static entries with desired colors.
	*/
	for (r=0; r<numRamps; ++r) {
	    int rampBase = r * rampSize + 10;
	    PALETTEENTRY *pe = &pPal->palPalEntry[rampBase];
	    int diffSize = (int) (rampSize * colors[r].ratio);
	    int specSize = rampSize - diffSize;

	    for (i=0; i<rampSize; ++i) {
		GLfloat *c0, *c1;
		GLint a;

		if (i < diffSize) {
		    c0 = colors[r].amb;
		    c1 = colors[r].diff;
		    a = (i * 255) / (diffSize - 1);
		} else {
		    c0 = colors[r].diff;
		    c1 = colors[r].spec;
		    a = ((i - diffSize) * 255) / (specSize - 1);
		}

		pe[i].peRed = (BYTE) (a * (c1[0] - c0[0]) + 255 * c0[0]);
		pe[i].peGreen = (BYTE) (a * (c1[1] - c0[1]) + 255 * c0[1]);
		pe[i].peBlue = (BYTE) (a * (c1[2] - c0[2]) + 255 * c0[2]);
		pe[i].peFlags = PC_NOCOLLAPSE;
	    }

	    colors[r].indexes[0] = rampBase;
	    colors[r].indexes[1] = rampBase + (diffSize-1);
	    colors[r].indexes[2] = rampBase + (rampSize-1);
	}

	/*
	** Initialize any remaining non-static entries.
	*/
	for (i=0; i<extra; ++i) {
	    int index = numRamps*rampSize+10+i;
	    PALETTEENTRY *pe = &pPal->palPalEntry[index];

	    pe->peRed = (BYTE) 0;
	    pe->peGreen = (BYTE) 0;
	    pe->peBlue = (BYTE) 0;
	    pe->peFlags = PC_NOCOLLAPSE;
	}
    }

    hPalette = CreatePalette(pPal);
    free(pPal);

    if (hPalette) {
	SelectPalette(hDC, hPalette, FALSE);
	RealizePalette(hDC);
    }
}

void
setupPixelFormat(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pfd = {
	sizeof(PIXELFORMATDESCRIPTOR),	/* size of this pfd */
	1,				/* version num */
	PFD_DRAW_TO_WINDOW |		/* support window */
	PFD_SUPPORT_OPENGL |		/* support OpenGL */
	PFD_DOUBLEBUFFER,		/* support double-buffering */
	PFD_TYPE_COLORINDEX,		/* color index mode */
	8,				/* 8-bit color depth */
	0, 0, 0, 0, 0, 0,		/* color bits (ignored) */
	0,				/* no alpha buffer */
	0,				/* alpha bits (ignored) */
	0,				/* no accumulation buffer */
	0, 0, 0, 0,			/* accum bits (ignored) */
	16,				/* depth buffer */
	0,				/* no stencil buffer */
	0,				/* no auxiliary buffers */
	PFD_MAIN_PLANE,			/* main layer */
	0,				/* reserved */
	0, 0, 0,			/* no layer, visible, damage masks */
    };
    int SelectedPixelFormat;
    BOOL retVal;

    SelectedPixelFormat = ChoosePixelFormat(hDC, &pfd);
    if (SelectedPixelFormat == 0) {
	MessageBox(WindowFromDC(hDC),
		"ChoosePixelFormat failed\n"
		"This application works best with an 8-bit\n"
		"(256 color) display mode\n",
		"Error",
		MB_ICONERROR | MB_OK);
	exit(1);
    }

    retVal = SetPixelFormat(hDC, SelectedPixelFormat, &pfd);
    if (retVal != TRUE) {
	MessageBox(WindowFromDC(hDC), "SetPixelFormat failed", "Error",
		MB_ICONERROR | MB_OK);
	exit(1);
    }
}

LRESULT APIENTRY
WndProc(
    HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (message) {
    case WM_CREATE:
	/*
	** Set up for OpenGL rendering.  Bind the rendering context to
	** the same device context that the palette will be selected into.
	*/
	hDC = GetDC(hWnd);
	setupPixelFormat(hDC);
	setupPalette(hDC);
	hGLRC = wglCreateContext(hDC);
	wglMakeCurrent(hDC, hGLRC);
	if (!checkExtension("SGI_index_material")) {
	    char message[1024];

	    sprintf(message,
		"SGI_index_material is required by this application\n"
		"but is not supported by the current OpenGL renderer.\n\n"
		"Vendor: %s\nRenderer: %s\nVersion: %s",
		glGetString(GL_VENDOR),
		glGetString(GL_RENDERER),
		glGetString(GL_VERSION));

	    wglMakeCurrent(NULL, NULL);
	    wglDeleteContext(hGLRC);
	    hGLRC = NULL;

	    MessageBox(hWnd, message, "OpenGL Extension Required",
		    MB_ICONERROR | MB_OK);
	    exit(1);
	}
	init();
	idleFunc = idleRedraw;
	return 0;
    case WM_DESTROY:
	/*
	** Finish OpenGL rendering.
	*/
	idleFunc = NULL;
	if (hGLRC) {
	    wglMakeCurrent(NULL, NULL);
	    wglDeleteContext(hGLRC);
	}
	ReleaseDC(hWnd, hDC);
	PostQuitMessage(0);
	return 0;
    case WM_SIZE:
	if (hGLRC) {
	    winWidth = (int) LOWORD(lParam);
	    winHeight = (int) HIWORD(lParam);
	    resize();
	    return 0;
	}
    case WM_PALETTECHANGED:
	/*
	** Update palette mapping if this *is not* the active window.
	*/
	if (hGLRC && hPalette && (HWND) wParam != hWnd) {
	    UnrealizeObject(hPalette);
	    SelectPalette(hDC, hPalette, FALSE);
	    RealizePalette(hDC);
	    redraw();
	    return 0;
	}
	break;
    case WM_QUERYNEWPALETTE:
	/*
	** Update palette mapping if this *is* the active window.
	*/
	if (hGLRC && hPalette) {
	    UnrealizeObject(hPalette);
	    SelectPalette(hDC, hPalette, FALSE);
	    RealizePalette(hDC);
	    redraw();
	    return TRUE;
	}
	break;
    case WM_PAINT:
	/*
	** Update the window.  Don't use the device context returned by
	** BeginPaint as it won't have the right palette selected into it.
	*/
	if (hGLRC) {
	    PAINTSTRUCT ps;

	    BeginPaint(hWnd, &ps);
	    redraw();
	    EndPaint(hWnd, &ps);
	    return 0;
	}
	break;
    case WM_CHAR:
	switch ((int)wParam) {
	case VK_ESCAPE:
	    DestroyWindow(hWnd);
	    return 0;
	default:
	    break;
	}
	break;
    default:
	break;
    }

    /* Deal with any unprocessed messages */
    return DefWindowProc(hWnd, message, wParam, lParam);
}

int APIENTRY
WinMain(
    HINSTANCE hCurrentInst,
    HINSTANCE hPreviousInst,
    LPSTR lpszCmdLine,
    int nCmdShow)
{
    WNDCLASS wndClass;
    HWND hWnd;
    MSG msg;

    /* Define and register a window class */
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hCurrentInst;
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = className;
    RegisterClass(&wndClass);

    /* Figure out a default size for the window */
    winWidth = GetSystemMetrics(SM_CYSCREEN) / 3;
    winHeight = GetSystemMetrics(SM_CYSCREEN) / 3;

    /* Create a window of the previously defined class */
    hWnd = CreateWindow(
	className,		/* Window class's name */
	windowName,		/* Title bar text */
	WS_OVERLAPPEDWINDOW |	/* The window's style */
	WS_CLIPCHILDREN |
	WS_CLIPSIBLINGS,
	winX, winY,		/* Position */
	winWidth, winHeight,	/* Size */
	NULL,			/* Parent window's handle */
	NULL,			/* Menu handle */
	hCurrentInst,		/* Instance handle */
	NULL);			/* No additional data */

    /* Map the window to the screen */
    ShowWindow(hWnd, nCmdShow);

    /* Force the window to repaint itself */
    UpdateWindow(hWnd);

    /* Process Messages */
    while (1) {
	/* execute the idle function while there are no messages to process */
	while (idleFunc &&
	       PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == FALSE)
	{
	    (*idleFunc)();
	}
	if (GetMessage(&msg, NULL, 0, 0) != TRUE) {
	    break;
	}
	TranslateMessage(&msg);
	DispatchMessage(&msg);
    }

    return msg.wParam;
}

