/*
 * @(#)MballGL.c
 *
 * Copyright 2023 - 2024  David A. Bagley, bagleyd AT verizon.net
 *
 * Based sphere, Copyright (c) 2002, 2008 Paul Bourke <pbourke@swin.edu.au>
 * Utility function to create a unit sphere in GL.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *  8-Oct-98: dek          Released initial version of "glplanet"
 * 21-Mar-01: jwz AT jwz.org  Broke sphere routine out into its own file.
 * 28-Feb-02: jwz AT jwz.org  New implementation from Paul Bourke:
 *                         http://astronomy.swin.edu.au/~pbourke/opengl/sphere/
 */

#ifdef HAVE_OPENGL

/* Methods file for MballGL */

#include "MballP.h"
#include "MballGLP.h"

#define glTranslate(x,y,z) glTranslatef((float) x, (float) y, (float) z)

static float front_shininess[] =
{60.0};
static float front_specular[] =
{(float) 0.7, (float) 0.7, (float) 0.7, 1.0};
static float ambient[] =
{0.0, 0.0, 0.0, 1.0};
static float diffuse[] =
{1.0, 1.0, 1.0, 1.0};
static float position0[] =
{1.0, 0.0, 0.0, 0.0};
static float position1[] =
{0.0, 0.0, 1.0, 0.0};
static float position2[] =
{-1.0, 0.0, 0.0, 0.0};
static float position3[] =
{0.0, 0.0, -1.0, 0.0};
static float position4[] =
{0.0, 1.0, 0.0, 0.0};
static float position5[] =
{0.0, -1.0, 0.0, 0.0};
static float lmodel_ambient[] =
{0.5, 0.5, 0.5, 1.0};
static float lmodel_twoside[] =
{GL_TRUE};

static float wedge_material[MAX_WEDGES + 2][4] =
{{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{0.0, 0.0, 0.0, 1.0},
{0.0, 0.0, 0.0, 1.0}};

static Boolean madeCurrent = False;

#ifdef WINVER
static HGLRC hRC = NULL;
#else
static GLXContext *glXContext = (GLXContext *) NULL;
static Boolean setValuesPuzzleGL(Widget current, Widget request, Widget renew);
static void resizePuzzleGL(MballGLWidget w);
static void initializePuzzleGL(Widget request, Widget renew);
static void exposePuzzleGL(Widget renew, XEvent *event, Region region);
static void movePuzzleGLTl(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLTop(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLTr(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLLeft(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLRight(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLBl(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLBottom(MballGLWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleGLBr(MballGLWidget w,
	XEvent *event, char **args, int nArgs);

static char translationsGL[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>F11: MoveCcw()\n\
 <KeyPress>KP_Divide: MoveCcw()\n\
 <KeyPress>R5: MoveCcw()\n\
 <KeyPress>Home: MoveTl()\n\
 <KeyPress>KP_7: MoveTl()\n\
 <KeyPress>R7: MoveTl()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>F12: MoveCw()\n\
 <KeyPress>Begin: MoveCw()\n\
 <KeyPress>KP_5: MoveCw()\n\
 <KeyPress>R11: MoveCw()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <KeyPress>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>2: Wedge2()\n\
 <KeyPress>4: Wedge4()\n\
 <KeyPress>6: Wedge6()\n\
 <KeyPress>8: Wedge8()\n\
 <KeyPress>0: Wedge10()\n\
 <KeyPress>=: Wedge12()\n\
 <KeyPress>.: Wedge12()\n\
 <KeyPress>v: View()\n\
 <KeyPress>t: Perspective()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsListGL[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveCcw", (XtActionProc) movePuzzleCcw},
	{(char *) "MoveTl", (XtActionProc) movePuzzleGLTl},
	{(char *) "MoveTop", (XtActionProc) movePuzzleGLTop},
	{(char *) "MoveTr", (XtActionProc) movePuzzleGLTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleGLLeft},
	{(char *) "MoveCw", (XtActionProc) movePuzzleCw},
	{(char *) "MoveBr", (XtActionProc) movePuzzleGLBr},
	{(char *) "MoveRight", (XtActionProc) movePuzzleGLRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleGLBl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleGLBottom},
	{(char *) "MoveBr", (XtActionProc) movePuzzleGLBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Practice", (XtActionProc) practicePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Wedge2", (XtActionProc) wedge2ModePuzzle},
	{(char *) "Wedge4", (XtActionProc) wedge4ModePuzzle},
	{(char *) "Wedge6", (XtActionProc) wedge6ModePuzzle},
	{(char *) "Wedge8", (XtActionProc) wedge8ModePuzzle},
	{(char *) "Wedge10", (XtActionProc) wedge10ModePuzzle},
	{(char *) "Wedge12", (XtActionProc) wedge12ModePuzzle},
	{(char *) "View", (XtActionProc) viewPuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Perspective", (XtActionProc) perspectivePuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resourcesGL[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.width),
	 XtRString, (caddr_t) "300"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.height),
	 XtRString, (caddr_t) "400"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	{XtNwedgeColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[0]),
	 XtRString, (caddr_t) "yellow"},
	{XtNwedgeColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[1]),
	 XtRString, (caddr_t) "green"},
	{XtNwedgeColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[2]),
	 XtRString, (caddr_t) "SeaGreen"},
	{XtNwedgeColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[3]),
	 XtRString, (caddr_t) "blue"},
	{XtNwedgeColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[4]),
	 XtRString, (caddr_t) "cyan"},
	{XtNwedgeColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[5]),
	 XtRString, (caddr_t) "magenta"},
	{XtNwedgeColor6, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[6]),
	 XtRString, (caddr_t) "red"},
	{XtNwedgeColor7, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[7]),
	 XtRString, (caddr_t) "orange"},
	{XtNwedgeColor8, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[8]),
	 XtRString, (caddr_t) "pink"},
	{XtNwedgeColor9, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[9]),
	 XtRString, (caddr_t) "tan"},
	{XtNwedgeColor10, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[10]),
	 XtRString, (caddr_t) "LightSteelBlue"},
	{XtNwedgeColor11, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[11]),
	 XtRString, (caddr_t) "IndianRed"},
	{XtNpieceBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.delay),
	 XtRString, (caddr_t) "10"},
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNview, XtCView, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.view),
	 XtRString, (caddr_t) "1"},
	{XtNwedges, XtCWedges, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.wedges),
	 XtRString, (caddr_t) "8"}, /*DEFAULT_WEDGES */
	{XtNbands, XtCBands, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.bands),
	 XtRString, (caddr_t) "4"}, /*DEFAULT_BANDS */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.orient),
	 XtRString, (caddr_t) "FALSE"},	/* DEFAULT_ORIENT */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.practice),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PRACTICE */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.base),
	 XtRString, (caddr_t) "16"}, /* DEFAULT_BASE */
	{XtNperspective, XtCPerspective, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.perspective),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_PERSPECTIVE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNwedge, XtCWedge, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentWedge),
	 XtRString, (caddr_t) "-1"},
	{XtNband, XtCBand, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentBand),
	 XtRString, (caddr_t) "-1"},
	{XtNdirection, XtCDirection, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentDirection),
	 XtRString, (caddr_t) "-1"},
	{XtNcontrol, XtCControl, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentControl),
	 XtRString, (caddr_t) "0"},
	{XtNfast, XtCFast, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.currentFast),
	 XtRString, (caddr_t) "1"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(MballWidget, mball.select),
	 XtRCallback, (caddr_t) NULL}
};

MballGLClassRec mballGLClassRec =
{
	{
		(WidgetClass) & mballClassRec,	/* superclass */
		(char *) "MballGL",	/* class name */
		sizeof (MballGLRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzleGL,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsListGL,	/* actions */
		XtNumber(actionsListGL),	/* num actions */
		resourcesGL,	/* resources */
		XtNumber(resourcesGL),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		NULL,		/* destroy */
		(XtWidgetProc) resizePuzzleGL,	/* resize */
		(XtExposeProc) exposePuzzleGL,	/* expose */
		(XtSetValuesFunc) setValuesPuzzleGL,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		XtInheritAcceptFocus,	/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		translationsGL,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	},
	{
		0		/* ignore */
	}
};

WidgetClass mballGLWidgetClass = (WidgetClass) & mballGLClassRec;
#endif

static int
sphereSlices(MballGLWidget w)
{
	return 120 / w->mball.wedges;
}

static int
sphereStacks(MballGLWidget w)
{
	if (w->mball.bands < 120)
		return 120 / w->mball.bands;
	else
		return w->mball.bands;
}

static void
pickColor(MballGLWidget w, int c, int mono, float *material)
{
#ifdef WINVER
	struct tagColor {
		int red, green, blue;
	} color;
#define MAX_INTENSITY 0xFF
#else
	XColor color;
#define MAX_INTENSITY 0xFFFF
#endif

	switch (c) {
	case TOP_WEDGE:
	case TTR_WEDGE:
	case TR_WEDGE:
	case RIGHT_WEDGE:
	case BR_WEDGE:
	case BBR_WEDGE:
	case BOTTOM_WEDGE:
	case BBL_WEDGE:
	case BL_WEDGE:
	case LEFT_WEDGE:
	case TL_WEDGE:
	case TTL_WEDGE:
#ifdef WINVER
		color.red = GetRValue(w->mball.wedgeGC[c]);
		color.green = GetGValue(w->mball.wedgeGC[c]);
		color.blue = GetBValue(w->mball.wedgeGC[c]);
#else
		color.pixel = w->mball.wedgeColor[c];
		XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)),
			&color);
#endif
		break;
	case BORDER_WEDGE:
#ifdef WINVER
		color.red = GetRValue(w->mball.borderGC);
		color.green = GetGValue(w->mball.borderGC);
		color.blue = GetBValue(w->mball.borderGC);
#else
		color.pixel = w->mball.borderColor;
		XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)),
			&color);
#endif
		break;
	case NO_WEDGE:
	default:
#ifdef WINVER
		color.red = GetRValue(w->mball.inverseGC);
		color.green = GetGValue(w->mball.inverseGC);
		color.blue = GetBValue(w->mball.inverseGC);
#else
		color.pixel = w->mball.background;
		XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)),
			&color);
#endif
		break;
	}
#ifdef DEBUG
	(void) printf("%d: i%d %d %d\n", c, color.red, color.green, color.blue);
#endif
	if (mono) {
		/* really GrayScale */
		float intensity = (float) (0.3 * color.red +
			0.59 * color.green + 0.11 * color.blue);
#ifdef DEBUG
		(void) printf("m%g\n", intensity);
#endif
		material[0] = (float) intensity / MAX_INTENSITY;
		material[1] = material[0];
		material[2] = material[0];
	} else {
#if 1	/* Hack: Orange and Pink were getting washed out */
		if (c < BORDER_WEDGE && ((color.red != 0 && color.red != MAX_INTENSITY)
				|| (color.green != 0 && color.green != MAX_INTENSITY)
				|| (color.blue != 0 && color.blue != MAX_INTENSITY))) {

			material[0] = (GLfloat) color.red / (2 * MAX_INTENSITY);
			material[1] = (GLfloat) color.green / (2 * MAX_INTENSITY);
			material[2] = (GLfloat) color.blue / (2 * MAX_INTENSITY);
		} else
#endif
		{
			material[0] = (GLfloat) color.red / MAX_INTENSITY;
			material[1] = (GLfloat) color.green / MAX_INTENSITY;
			material[2] = (GLfloat) color.blue / MAX_INTENSITY;
		}
	}
#ifdef DEBUG
	(void) printf("%d: f%g %g %g\n", c, material[0], material[1], material[2]);
#endif
}

typedef float Matrix[3][3];
typedef float Vector[3];

static void
matrixMatrix(Matrix a, Matrix b, Matrix x)
{
	int i, j;

	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			x[i][j] = a[i][0] * b[0][j] +
				a[i][1] * b[1][j] +
				a[i][2] * b[2][j];
		}
	}
}

static void
matrixVector(Matrix a, Vector v, Vector x)
{
	int i;

	for (i = 0; i < 3; i++) {
		x[i] = a[i][0] * v[0] +
			a[i][1] * v[1] +
			a[i][2] * v[2];
	}
}

static void
setMatrix(float theta, float x, float y, float z, Matrix a)
{
	float s = (float) sin(theta * M_PI / 180.0);
	float c = (float) cos(theta * M_PI / 180.0);

	if (x != 0.0 && y == 0.0 && z == 0.0) {
		a[0][0] = 1.0, a[0][1] = 0.0; a[0][2] = 0.0;
		a[1][0] = 0.0, a[1][1] = c; a[1][2] = -s;
		a[2][0] = 0.0, a[2][1] = s; a[2][2] = c;
	} else if (y != 0.0 && x == 0.0 && z == 0.0) {
		a[0][0] = c, a[0][1] = 0.0; a[0][2] = s;
		a[1][0] = 0.0, a[1][1] = 1.0; a[1][2] = 0.0;
		a[2][0] = -s, a[2][1] = 0.0; a[2][2] = c;
	} else if (z != 0.0 && x == 0.0 && y == 0.0) {
		a[0][0] = c, a[0][1] = -s; a[0][2] = 0.0;
		a[1][0] = s, a[1][1] = c; a[1][2] = 0.0;
		a[2][0] = 0.0, a[2][1] = 0.0; a[2][2] = 1.0;
	}
}

typedef struct { GLfloat x, y, z; } XYZ;

/* TODO, draw a wire frame of wedge where it is composed of
   more than one stack and slice, as cannot tell where one wedge
   ends and another begins if they are the same color */

static int
drawWedge(MballGLWidget w, Boolean wire, int wedge, int band)
{
	int stacks = w->mballGL.stacksPerBand * w->mball.bands;
	int slices = w->mballGL.slicesPerWedge * w->mball.wedges;
	int polys = 0;
	int i, j;
	double theta1, theta2, theta3;
	XYZ e, p;
	XYZ la = {0, 0, 0}, lb = {0, 0, 0};
	XYZ c = {0, 0, 0};	/* center */
	double r = 1.0;		/* radius */
	int stacks2 = stacks * 2;
	int firstStack, firstSlice;
	int lastStack, lastSlice;

	if (r < 0)
		r = -r;
	if (slices < 0)
		slices = -slices;
	if (slices < 4 || stacks < 2 || r <= 0) {
		glBegin(GL_POINTS);
		glVertex3f(c.x, c.y, c.z);
		glEnd();
		return 1;
	}
	glFrontFace(GL_CW);
	firstStack = band * w->mballGL.stacksPerBand;
	lastStack = firstStack + w->mballGL.stacksPerBand - 1;
	if (wire)
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, wedge_material[MAX_WEDGES]);
	for (j = firstStack; j <= lastStack; j++) {
		theta1 = 2 * j * M_PI / stacks2 - M_PI_2;
		theta2 = 2 * (j + 1) * M_PI / stacks2 - M_PI_2;
		glBegin((wire) ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
		firstSlice = wedge * w->mballGL.slicesPerWedge;
		lastSlice = firstSlice + w->mballGL.slicesPerWedge;
		for (i = firstSlice; i <= lastSlice; i++) {
#ifdef WEDGE_EDGE
			if (i == firstSlice || i == lastSlice || j == firstStack || j == lastStack)
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, wedge_material[BORDER_WEDGE]);
			else
#else
			if (!wire)
#endif
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, wedge_material[w->mball.mballLoc[wedge][w->mball.bands - 1 - band].wedge]);
			theta3 = i * 2 * M_PI / slices;
			if (wire && i != firstSlice) {
				glVertex3f(lb.x, lb.y, lb.z);
				glVertex3f(la.x, la.y, la.z);
			}
			e.x = (float) (cos(theta2) * cos(theta3));
			e.y = (float) sin(theta2);
			e.z = (float) (cos(theta2) * sin(theta3));
			p.x = (float) (c.x + r * e.x);
			p.y = (float) (c.y + r * e.y);
			p.z = (float) (c.z + r * e.z);
			glNormal3f(e.x, e.y, e.z);
			glTexCoord2f((float) (i / (double) slices),
				(float) (2 * (j + 1) / (double) stacks2));
			glVertex3f(p.x, p.y, p.z);
			if (wire)
				la = p;
			e.x = (float) (cos(theta1) * cos(theta3));
			e.y = (float) sin(theta1);
			e.z = (float) (cos(theta1) * sin(theta3));
			p.x = (float) (c.x + r * e.x);
			p.y = (float) (c.y + r * e.y);
			p.z = (float) (c.z + r * e.z);
			glNormal3f(e.x, e.y, e.z);
			glTexCoord2f((float) (i / (double) slices),
				(float) (2 * j / (double) stacks2));
			glVertex3f(p.x, p.y, p.z);
			if (wire)
				lb = p;
			polys++;
		}
		glEnd();
	}
	return polys;
}

static void
drawBand(MballGLWidget w, int band) {
	int wedge;
	for (wedge = 0; wedge < w->mball.wedges; wedge++)
		drawWedge(w, False, wedge, w->mball.bands - 1 - band);
}

static void
drawNotBand(MballGLWidget w, int notBand) {
	int wedge, band;
	for (wedge = 0; wedge < w->mball.wedges; wedge++)
		for (band = 0; band < w->mball.bands; band++)
			if (notBand != band)
				drawWedge(w, False, wedge, w->mball.bands - 1 - band);
}

static void
drawHalf(MballGLWidget w, int offset) {
	int wedge, band;
	for (wedge = 0; wedge < w->mball.wedges / 2; wedge++)
		for (band = 0; band < w->mball.bands; band++)
			drawWedge(w, False, (wedge + offset) % w->mball.wedges,
				 w->mball.bands - 1 - band);
}

static void
drawWedges(MballGLWidget w, int wedge, int band, int direction,
		Boolean use, Boolean all)
{
	int half = w->mball.wedges / 2;
	int whole = w->mball.wedges;

	switch (direction) {
	case TOP:
	case BOTTOM:
		if (all || use) {
			glPushMatrix();
			drawHalf(w, (wedge / half) * half);
			glPopMatrix();
		} else if (!all)
			drawHalf(w, ((wedge / half) * half + half) % whole);
		break;
	case TTR:
	case BBL:
		if (w->mball.wedges % 12 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 11 * half / 6) % whole) / half) * half + half / 6) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 11 * half / 6) % whole) / half) * half + 7 * half / 6) % whole);
		} else if (w->mball.wedges % 10 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 9 * half / 5) % whole) / half) * half + half / 5) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 9 * half / 5) % whole) / half) * half + 6 * half / 5) % whole);
		}
		break;
	case TR:
	case BL:
		if (w->mball.wedges % 10 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 8 * half / 5) % whole) / half) * half + 2 * half / 5) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 8 * half / 5) % whole) / half) * half + 7 * half / 5) % whole);
		} else if (w->mball.wedges % 8 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 7 * half / 4) % whole) / half) * half + half / 4) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 7 * half / 4) % whole) / half) * half + 5 * half / 4) % whole);
		} else if (w->mball.wedges % 6 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 5 * half / 3) % whole) / half) * half + half / 3) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 5 * half / 3) % whole) / half) * half + 2 * whole / 3) % whole);
		}
		break;
	case RIGHT:
	case LEFT:
		if (w->mball.wedges % 4 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 3 * half / 2) % whole) / half) * half + half / 2) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 3 * half / 2) % whole) / half) * half + 3 * half / 2) % whole);
		}
		break;
	case BR:
	case TL:
		if (w->mball.wedges % 10 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 7 * half / 5) % whole) / half) * half + 3 * half / 5) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 7 * half / 5) % whole) / half) * half + 8 * half / 5) % whole);
		} else if (w->mball.wedges % 8 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 5 * half / 4) % whole) / half) * half + 3 * half / 4) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 5 * half / 4) % whole) / half) * half + 7 * half / 4) % whole);
		} else if (w->mball.wedges % 6 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 4 * half / 3) % whole) / half) * half + whole / 3) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 4 * half / 3) % whole) / half) * half + 5 * half / 3) % whole);
		}
		break;
	case BBR:
	case TTL:
		if (w->mball.wedges % 12 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 13 * half / 6) % whole) / half) * half + 11 * half / 6) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 13 * half / 6) % whole) / half) * half + 5 * half / 6) % whole);
		} else if (w->mball.wedges % 10 == 0) {
			if (all || use) {
				glPushMatrix();
				drawHalf(w, ((((wedge + 11 * half / 5) % whole) / half) * half + 9 * half / 5) % whole);
				glPopMatrix();
			} else if (!all)
				drawHalf(w, ((((wedge + 11 * half / 5) % whole) / half) * half + 4 * half / 5) % whole);
		}
		break;
	case CW:
	case CCW:
		if (all || use) {
			glPushMatrix();
			drawBand(w, band);
			glPopMatrix();
		} else if (!all)
			drawNotBand(w, band);
		break;
	}
}

static void
rotateWedgeSlice(MballGLWidget w, GLfloat rotateStep,
		GLfloat x, GLfloat y, GLfloat z,
		int wedge, int band, int direction)
{
	int b;
	glPushMatrix();
	glRotatef(rotateStep, x, y, z);
	if (w->mballGL.movement.control) {
		if (direction == CW || direction == CCW)
			for (b = 0; b < w->mball.bands; b++)
				drawWedges(w, wedge, b, direction, False, True);
		else
			for (b = 0; b < 2; b++)
				drawWedges(w, b * w->mball.wedges / 2, band, direction, False, True);
	} else
		drawWedges(w, wedge, band, direction, True, False);
	glPopMatrix();
	if (!w->mballGL.movement.control)
		drawWedges(w, wedge, band, direction, False, False);
}

static void
drawSphere(MballGLWidget w)
{
	GLfloat rotateStep = 0.0;
	int wedge = w->mballGL.movement.wedge;
	int band = w->mballGL.movement.band;
	int direction = w->mballGL.movement.direction;

	if (wedge == NO_WEDGE || direction == IGNORE_DIR) {
		drawNotBand(w, -1);
	} else {
		float sqrt3 = (float) 1.7320508075688;
		float tanpidot3 = (float) 1.3763819204711; /* tan(3*pi/10) */
		float tanpidot = (float) 0.3249196962329; /* tan(pi/10) */
		rotateStep = (float) w->mballGL.rotateStep;
		switch(direction) {
		case TOP:
			rotateWedgeSlice(w, rotateStep, 0, 0, -1,
				wedge, band, direction);
			break;
		case TTR:
			if (w->mball.wedges % 12 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, -sqrt3,
					wedge, band, direction);
			else if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, -tanpidot3,
					wedge, band, direction);
			break;
		case TR:
			if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, -tanpidot,
					wedge, band, direction);
			else if (w->mball.wedges % 8 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, -1,
					wedge, band, direction);
			else if (w->mball.wedges % 6 == 0)
				rotateWedgeSlice(w, rotateStep, sqrt3, 0, -1,
					wedge, band, direction);
			break;
		case RIGHT:
			if (w->mball.wedges % 4 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, 0,
					wedge, band, direction);
			break;
		case BR:
			if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, tanpidot,
					wedge, band, direction);
			else if (w->mball.wedges % 8 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, 1,
					wedge, band, direction);
			else if (w->mball.wedges % 6 == 0)
				rotateWedgeSlice(w, rotateStep, sqrt3, 0, 1,
					wedge, band, direction);
			break;
		case BBR:
			if (w->mball.wedges % 12 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, sqrt3,
					wedge, band, direction);
			else if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, 1, 0, tanpidot3,
					wedge, band, direction);
			break;
		case BOTTOM:
			rotateWedgeSlice(w, rotateStep, 0, 0, 1,
				wedge, band, direction);
			break;
		case BBL:
			if (w->mball.wedges % 12 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, sqrt3,
					wedge, band, direction);
			else if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, tanpidot3,
					wedge, band, direction);
			break;
		case BL:
			if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, tanpidot,
					wedge, band, direction);
			else if (w->mball.wedges % 8 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, 1,
					wedge, band, direction);
			else if (w->mball.wedges % 6 == 0)
				rotateWedgeSlice(w, rotateStep, -sqrt3, 0, 1,
					wedge, band, direction);
			break;
		case LEFT:
			if (w->mball.wedges % 4 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, 0,
					wedge, band, direction);
			break;
		case TL:
			if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, -tanpidot,
					wedge, band, direction);
			else if (w->mball.wedges % 8 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, -1,
					wedge, band, direction);
			else if (w->mball.wedges % 6 == 0)
				rotateWedgeSlice(w, rotateStep, -sqrt3, 0, -1,
					wedge, band, direction);
			break;
		case TTL:
			if (w->mball.wedges % 12 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, -sqrt3,
					wedge, band, direction);
			else if (w->mball.wedges % 10 == 0)
				rotateWedgeSlice(w, rotateStep, -1, 0, -tanpidot3,
					wedge, band, direction);
			break;
		case CW:
			rotateWedgeSlice(w, rotateStep, 0, -1, 0,
				wedge, band, direction);
			break;
		case CCW:
			rotateWedgeSlice(w, rotateStep, 0, 1, 0,
				wedge, band, direction);
			break;
		}
	}
}

void
movePiecesGL(MballGLWidget w, int wedge, int band, int direction,
		Boolean control, int fast)
{
	int i, f;
	w->mballGL.movement.wedge = wedge;
	w->mballGL.movement.band = band;
	w->mballGL.movement.direction = direction;
	w->mballGL.movement.control = control;
	w->mballGL.rotateStep = 0.0;
	w->mballGL.angleStep = 0.0;
	f = (w->mballGL.numTurnInc < fast) ? 1 : fast;
	for (i = 0; i < w->mballGL.numTurnInc / f; i++) {
		double increment;
		if (direction == CW || direction == CCW)
			increment = (360.0 / w->mball.wedges) * f / w->mballGL.numTurnInc;
		else
			increment = 180.0 * f / w->mballGL.numTurnInc;
		w->mballGL.rotateStep += increment;
		drawAllPiecesGL(w);
		Sleep((unsigned int) ((w->mball.delay /
			(w->mballGL.numTurnInc * f)) * 40.0));
		w->mball.currentWedge = -1;
		w->mball.currentBand = -1;
	}
	w->mballGL.angleStep = 0.0;
	w->mballGL.movement.wedge = NO_WEDGE;
	w->mballGL.movement.band = NO_BAND;
	w->mballGL.movement.direction = IGNORE_DIR;
	w->mballGL.movement.control = False;
}

void
drawAllPiecesGL(MballGLWidget w)
{
#ifdef WINVER
	wglMakeCurrent(w->core.hDC, hRC);
#else
	if (!glXMakeCurrent(XtDisplay(w), XtWindow(w), *glXContext)) {
		DISPLAY_WARNING("Draw All GL error");
	}
#endif
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix();
	glTranslate(0.0, 0.0, -10.0);
	if (w->core.height > w->core.width)
		glScalef(SCALE, (SCALE * (float) w->core.width / (float) w->core.height), SCALE);
	else
		glScalef((SCALE * (float) w->core.height / (float) w->core.width), SCALE, SCALE);
	switch (w->mball.view) {
	case 0: /* v  vertical */
		glRotatef((GLfloat) (-3 * w->mballGL.step) / (GLfloat) 1.0, 0, 1, 0);
		glRotatef((GLfloat) (w->mballGL.step) / (GLfloat) -2.5, 0, 0, 1);
		break;
	case 1: /* ^  vertical */
		glRotatef((GLfloat) (-3 * w->mballGL.step) / (GLfloat) 1.0, 0, 1, 0);
		glRotatef((GLfloat) (w->mballGL.step) / (GLfloat) 2.5, 0, 0, 1);
		break;
	default:
		break;
	}
	drawSphere(w);
	glPopMatrix();
	glFlush();
#ifdef WINVER
	SwapBuffers(w->core.hDC);
#else
	glXSwapBuffers(XtDisplay(w), XtWindow(w));
#endif
}

/* UNUSED */
/*void
drawFrameGL(MballGLWidget w, Boolean focus)
{
}*/

/*static int
normalizePosition(float x, float y, int size)
{
	return 0;
}*/

#if 0
Boolean
selectPiecesGL(MballGLWidget w, int x, int y, int *wedge, int *band)
{
	/* Using gluUnProject By: Luke Benstead */
	/* http://nehe.gamedev.net/data/articles/article.asp?article=13 */

	GLint viewport[4];
	GLdouble modelview[16];
	GLdouble projection[16];
	GLfloat winX, winY, winZ;
	GLdouble posX, posY, posZ;
	Matrix a, b, r;
	Vector t, world, nw;
	int view = 1;

	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	glGetIntegerv(GL_VIEWPORT, viewport);

	winX = (float) x;
	winY = (float) viewport[3] - (float) y;
	glReadPixels(x, (int) winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
		&winZ);
	gluUnProject(winX, winY, winZ, modelview, projection, viewport,
		&(posX), &(posY), &(posZ));
	t[0] = (float) posX;
	t[1] = (float) posY;
	t[2] = (float) (posZ + 10);
	if (w->core.height > w->core.width)
		t[1] *= (float) w->core.height / (float) w->core.width;
	else
		t[0] *= (float) w->core.width / (float) w->core.height;
	*wedge = -1;
	/* *band = -1; */
	if (winZ == 1) {
		return False;
	}
	world[0] = t[0], world[1] = t[1], world[2] = t[2];
	switch (view) {
	case 0: /* -<  vertical */
		setMatrix((float) (-w->mballGL.step) / (float) 2.0, 1.0, 0.0, 0.0, a);
		setMatrix((float) (-w->mballGL.step) / (float) 2.5, 0.0, 1.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[0] >= nw[2] && nw[1] >= nw[2]) {
			*wedge = 0;
			*band = normalizePosition(world[0], world[2], w->mball.bands);
		} else if (nw[1] >= nw[0] && nw[2] >= nw[0]) {
			*wedge = 1;
			*band = normalizePosition(world[2], -world[1], w->mball.bands);
		} else if (nw[0] >= nw[1] && nw[2] >= nw[1]) {
			*wedge = 2;
			*band = normalizePosition(world[0], -world[1], w->mball.bands);
		}
		break;
	case 1: /* ^  !vertical */
		setMatrix((float) (3 * w->mballGL.step) / (float) 2.0, 0.0, 1.0, 0.0, a);
		setMatrix((float) w->mballGL.step / (float) 2.65, 1.0, 0.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[0] <= nw[1] && nw[0] <= nw[2]) {
			*wedge = 0;
			*band = normalizePosition(-world[2], -world[1], w->mball.bands);
		} else if (nw[1] <= nw[0] && nw[1] <= nw[2]) {
			*wedge = 2;
			*band = normalizePosition(world[0], -world[2], w->mball.bands);
		} else if (nw[2] <= nw[0] && nw[2] <= nw[1]) {
			*wedge = 1;
			*band = normalizePosition(world[0], world[1], w->mball.bands);
		}
		break;
	case 2: /* -<  vertical */
		setMatrix((float) (3 * w->mballGL.step) / (float) 2.0, 1.0, 0.0, 0.0, a);
		setMatrix((float) w->mballGL.step / (float) 2.65, 0.0, 1.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[1] >= nw[0] && nw[1] >= nw[2]) {
			*wedge = 0;
			*band = normalizePosition(world[0], world[2], w->mball.bands);
		} else if (nw[0] >= nw[1] && nw[0] >= nw[2]) {
			*wedge = 1;
			*band = normalizePosition(world[2], -world[1], w->mball.bands);
		} else if (nw[2] >= nw[0] && nw[2] >= nw[1]) {
			*wedge = 2;
			*band = normalizePosition(world[0], -world[1], w->mball.bands);
		}
		*wedge = 3;
		break;
	case 3: /* Y  !vertical */
		setMatrix((float) (-w->mballGL.step) / (float) 2.0, 0.0, 1.0, 0.0, a);
		setMatrix((float) (-w->mballGL.step) / (float) 2.5, 1.0, 0.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[0] <= nw[1] && nw[0] <= nw[2]) {
			*wedge = 0;
			*band = normalizePosition(-world[2], -world[1], w->mball.bands);
		} else if (nw[1] <= nw[0] && nw[1] <= nw[2]) {
			*wedge = 1;
			*band = normalizePosition(world[0], -world[2], w->mball.bands);
		} else if (nw[2] <= nw[0] && nw[2] <= nw[1]) {
			*wedge = 2;
			*band = normalizePosition(world[0], world[1], w->mball.bands);
		}
		*wedge = 3;
		break;
	default:
		break;
	}
#ifdef DEBUG
	(void) printf("view %d, x %d, y %d\n", w->mball.view, x, y);
	(void) printf(" wx %g, wy %g, wz %g\n", winX, winY, winZ);
	(void) printf("  px %g, py %g, pz %g\n", posX, posY, posZ);
	(void) printf("   tx %g, ty %g, tz %g\n", t[0], t[1], t[2]);
	(void) printf("    rx %g, ry %g, rz %g\n",
		world[0], world[1], world[2]);
	(void) printf("     wedge %d, band %d\n", *wedge, *band);
#endif
	/* FIXME need to determine wedge and band
	return True;*/
	return False;
}
#endif

Boolean
narrowSelectionGL(MballGLWidget w,
		int *wedge, int *band, int *direction)
{
	return True;
}

/* TODO */
Boolean
positionToSectorGL(MballGLWidget w, int x, int y,
		int *sector, int *view)
{
	/* Using gluUnProject By: Luke Benstead */
	/* http://nehe.gamedev.net/data/articles/article.asp?article=13 */

	GLint viewport[4];
	GLdouble modelview[16];
	GLdouble projection[16];
	GLfloat winX, winY, winZ;
	GLdouble posX, posY, posZ;
	Matrix a, b, r;
	Vector t, world, nw;
	int wedge = 0, band = 0;

	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	glGetIntegerv(GL_VIEWPORT, viewport);

	winX = (float) x;
	winY = (float) viewport[3] - (float) y;
	glReadPixels(x, (int) winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
		&winZ);
	gluUnProject(winX, winY, winZ, modelview, projection, viewport,
		&(posX), &(posY), &(posZ));
	t[0] = (float) posX;
	t[1] = (float) posY;
	t[2] = (float) (posZ + 10);
	if (w->core.height > w->core.width)
		t[1] *= (float) w->core.height / (float) w->core.width;
	else
		t[0] *= (float) w->core.width / (float) w->core.height;
	if (winZ == 1) {
		return False;
	}
	*view = w->mball.view;
	switch (*view) {
	case 0: /* v  !vertical */
		setMatrix((float) (-w->mballGL.step) / (float) 2.0, 0.0, 1.0, 0.0, a);
		setMatrix((float) (-w->mballGL.step) / (float) 2.65, 1.0, 0.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[0] <= nw[2]) {
			wedge = w->mball.wedges / 2 - 1;
		} else {
			wedge = w->mball.wedges / 2;
		}
		band = w->mball.bands - 1;
		break;
	case 1: /* ^  !vertical */
		setMatrix((float) (3 * w->mballGL.step) / (float) 2.0, 0.0, 1.0, 0.0, a);
		setMatrix((float) w->mballGL.step / (float) 2.5, 1.0, 0.0, 0.0, b);
		matrixMatrix(a, b, r);
		matrixVector(r, t, world);
		nw[0] = -world[0] / (float) 2.0;
		nw[1] = world[1] / (float) 2.0;
		nw[2] = world[2] / (float) 2.0;
		if (nw[0] <= nw[2]) {
			wedge = w->mball.wedges / 2;
		} else {
			wedge = w->mball.wedges / 2 - 1;
		}
		band = 0;
		break;
	default:
		break;
	}
	*sector = wedge + w->mball.wedges * band;
#ifdef DEBUG
	(void) printf("view %d, x %d, y %d\n", w->mball.view, x, y);
	(void) printf("     wedge %d, band %d, view %d\n", wedge, band, *view);
#endif
	return True;
}

#ifdef WINVER
static Boolean
setupPixelFormat(MballGLWidget w, BYTE type, DWORD flags)
{
	PIXELFORMATDESCRIPTOR pfd;
	int pixelFormat;

	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
	pfd.dwLayerMask = PFD_MAIN_PLANE;
	/*pfd.cColorBits = 8;
	pfd.cDepthBits = 16;*/
	pfd.iPixelType = type;
	pfd.cColorBits = 24;
	pfd.cDepthBits = 32;

	if ((pixelFormat = ChoosePixelFormat(w->core.hDC, &pfd)) == 0) {
		DWORD err;
		char *buf1;

		err = GetLastError();
		/* 6 ERROR_INVALID_HANDLE */
		intCat(&buf1, "ChoosePixelFormat failed ", (int) err);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return FALSE;
	}

	if (SetPixelFormat(w->core.hDC, pixelFormat, &pfd) == FALSE) {
		MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);
		return FALSE;
	}
	DescribePixelFormat(w->core.hDC, pixelFormat,
		sizeof(PIXELFORMATDESCRIPTOR), &pfd);
	return TRUE;
}

#else

static Boolean
setValuesPuzzleGL(Widget current, Widget request, Widget renew)
{
	MballGLWidget c = (MballGLWidget) current, w = (MballGLWidget) renew;
	Boolean redraw = False;

	if (w->mball.view != c->mball.view) {
		resizePuzzleGL(w);
		redraw = True;
	}
	if (w->mball.wedges != c->mball.wedges ||
			w->mball.bands != c->mball.bands) {
		resetPieces((MballWidget) w);
		resizePuzzleGL(w);
		redraw = True;
	}
	if (w->mball.delay != c->mball.delay) {
		w->mballGL.numTurnInc = ((w->mball.delay < MAX_TURN_INC) ?
			w->mball.delay + 1 : MAX_TURN_INC);
	}
	return (redraw);
}

static GLXContext *
initGL(MballGLWidget w) {
	XVisualInfo xviIn, *xviOut;
	int numVis;

	/*XGetWindowAttributes(XtDisplay(w), XtWindow(w), &xgwa); */
	xviIn.screen = DefaultScreen(XtDisplay(w));
	xviIn.visualid = XVisualIDFromVisual(DefaultVisual(XtDisplay(w),
		xviIn.screen));
	xviOut = XGetVisualInfo(XtDisplay(w), VisualScreenMask | VisualIDMask,
		&xviIn, &numVis);
	if (!xviOut) {
		XtWarning("Could not get XVisualInfo");
		return (GLXContext *) NULL;
	}
	if (glXContext)
		free(glXContext);
	if ((glXContext = (GLXContext *) malloc(sizeof (GLXContext))) ==
			NULL) {
		DISPLAY_ERROR("Not enough memory for glx info, exiting.");
	}
	/* assertion "glx_dpy" failed on Cygwin */
	if (!glXQueryExtension(XtDisplay(w), NULL, NULL)) {
		XtWarning("Could not find GLX");
		return (GLXContext *) NULL;
	}
	*glXContext = glXCreateContext(XtDisplay(w), xviOut, 0, GL_TRUE);
	(void) XFree((char *) xviOut);
	if (!*glXContext) {
		XtWarning("Could not create rendering context");
		return (GLXContext *) NULL;
	}
	return glXContext;
}
#endif

#ifndef WINVER
static
#endif
void
resizePuzzleGL(MballGLWidget w)
{
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif
	glViewport(0, 0, (GLint) w->core.width, (GLint) w->core.height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 15.0);
	glMatrixMode(GL_MODELVIEW);
	w->mballGL.slicesPerWedge = sphereSlices(w);
	w->mballGL.stacksPerBand = sphereStacks(w);
}

static void
makeCurrentGL(MballGLWidget w)
{
	GLboolean rgbaMode;

#ifdef WINVER
	wglMakeCurrent(w->core.hDC, hRC);
#else
	if (!glXMakeCurrent(XtDisplay(w), XtWindow(w), *glXContext)) {
		DISPLAY_WARNING("GL error");
	}
#endif
	madeCurrent = True;
	/* True Color junk */
	glGetBooleanv(GL_RGBA_MODE, &rgbaMode);
	if (!rgbaMode) {
#ifdef WINVER
		glClearIndex(0.0);
#else
		glIndexi(WhitePixel(XtDisplay(w),
			DefaultScreen(XtDisplay(w))));
		glClearIndex((float) BlackPixel(XtDisplay(w),
			DefaultScreen(XtDisplay(w))));
#endif
	}
	resizePuzzleGL(w);

	glDrawBuffer(GL_BACK);
	glClearDepth(1.0);
	glClearColor(wedge_material[NO_WEDGE][0], wedge_material[NO_WEDGE][1],
		wedge_material[NO_WEDGE][2], wedge_material[NO_WEDGE][3]);
	glColor3f(1.0, 1.0, 1.0);

	glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, position0);
	glLightfv(GL_LIGHT1, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, position1);
	glLightfv(GL_LIGHT2, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT2, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT2, GL_POSITION, position2);
	glLightfv(GL_LIGHT3, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT3, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT3, GL_POSITION, position3);
	glLightfv(GL_LIGHT4, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT4, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT4, GL_POSITION, position4);
	glLightfv(GL_LIGHT5, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT5, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT5, GL_POSITION, position5);
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
	glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHT2);
	glEnable(GL_LIGHT3);
	glEnable(GL_LIGHT4);
	glEnable(GL_LIGHT5);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE);
	glEnable(GL_CULL_FACE);
	glShadeModel(GL_FLAT);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, front_shininess);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, front_specular);

	w->mballGL.step = 90;
	w->mballGL.rotateStep = 0.0;
}

#ifndef WINVER
static void
initializePuzzleGL(Widget request, Widget renew)
{
	MballGLWidget w = (MballGLWidget) renew;
	int i;

	setAllColors((MballWidget) w);
	if (initGL(w) == (GLXContext *) NULL) {
		w->mball.dim = 0;
		return;
	}
	w->mball.dim = 4; /* 2 and 3 already taken */
	for (i = 0; i < MAX_WEDGES + 2; i++)
		pickColor(w, i, w->mball.mono, wedge_material[i]);
	w->mballGL.numTurnInc = ((w->mball.delay < MAX_TURN_INC) ?
		w->mball.delay + 1 : MAX_TURN_INC);
	resizePuzzleGL(w);
}
#endif

#ifndef WINVER
static
#endif
void
exposePuzzleGL(
#ifdef WINVER
MballGLWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifdef WINVER
	if (hRC == NULL) {
		BYTE type;
		int i;
		DWORD flags = PFD_DOUBLEBUFFER;

#if 1
		type = PFD_TYPE_RGBA;
#else
		type = PFD_TYPE_COLORINDEX;
#endif
		(void) setupPixelFormat(w, type, flags);
		hRC = wglCreateContext(w->core.hDC);
		w->mballGL.movement.wedge = NO_WEDGE;
		w->mballGL.movement.band = NO_BAND;
		w->mballGL.movement.direction = IGNORE_DIR;
		w->mballGL.movement.control = False;
		w->mballGL.rotateStep = 0.0;
		w->mballGL.angleStep = 0.0;
		w->mball.dim = 4; /* 2 and 3 already taken */
		for (i = 0; i < MAX_WEDGES + 2; i++)
			pickColor(w, i, w->mball.mono, wedge_material[i]);
		w->mballGL.numTurnInc = ((w->mball.delay < MAX_TURN_INC) ?
			w->mball.delay + 1 : MAX_TURN_INC);
		resizePuzzleGL(w);
	}
#else
	MballGLWidget w = (MballGLWidget) renew;

	if (!w->core.visible)
		return;
	if (w->mball.dim != 4)
		return;
#endif
	if (!madeCurrent) {
		makeCurrentGL(w);
	}
	/*drawFrameGL(w, w->mball.focus);*/
	drawAllPiecesGL(w);
}

#ifndef WINVER
static void
movePuzzleGLTl(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		TL, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLTop(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		TOP, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLTr(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		TR, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLLeft(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		LEFT, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLRight(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		RIGHT, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLBl(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		BL, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLBottom(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		BOTTOM, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleGLBr(MballGLWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput((MballWidget) w, event->xbutton.x, event->xbutton.y,
		BR, (int) (event->xkey.state & ControlMask));
}
#endif

#else
int x = 0; /* non empty source */
#endif
