
/* compile as

	cc -o capping capping.c -lGL -lGLU -lX11 -lm

   -- seth.

*/

/*
 
Kurt Akeley  (kurt@sgi.com)

*/

/*
** Porting bugs
**
**  1.	Can't reduce motion by using SHIFT and CTRL keys.
*/

/****************************************************************************
Copyright 1991 by Silicon Graphics Incorporated, Mountain View, California.

                        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 Silicon Graphics not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL SILICON GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

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

/* 
 *	Kurt Akeley
 *	February 1991
 *	Simple capping demonstration
 *
 *	  When a polygonal surface representation of a solid object is
 *	  clipped, the inside of the "solid" is revealed.  This
 *	  program demonstrates a technique to "cap" the solid, so that
 *	  it appears solid even when clipped.  In order to do this, it
 *	  draws each object twice, once to render the visible surfaces,
 *	  and again counting the number of accesses at each pixel.
 *	  Finally, the clipping planes themselves are drawn, with pixels
 *	  actually modified only where the pixel access count in the
 *	  stencil buffer is odd (i.e. where the inside of a solid was
 *	  revealed).
 *
 *	  When invoked with no arguments, three internally defined models
 *	  are displayed (hit the space bar to toggle between models).
 *	  If an argument is given, it is expected to be the name of a
 *	  flip-format .bin file.  If this file is found in either the
 *	  local directory or in /usr/demos/data/models, it replaces the
 *	  first internally defined model.
 *
 *	  For this code to work correctly, the model must be built of
 *	  proper polygonal surfaces.  Such surfaces separate space into
 *	  inside and outside volumes (perhaps more than one of each).
 *	  They are constructed of polygons whose adjacent edges share
 *	  vertexes and do not overlap.  The martini glass (martini.bin)
 *	  is an example of such a model.  None of the other models
 *	  currently in the models directory is a proper solid.
 *
 *	  It is acceptable for the model to include intersecting proper
 *	  polygon solids, as demonstrated by the third internal model.
 *	  When intersections are viewed normally, the first solid drawn
 *	  'wins'.  In orange-caps mode, however, intersection regions
 *	  are hilighted in yellow, rather than in the standard orange.
 *
 *	6 November 1991
 *	Added interference checking in "orange cap" mode.
 *
 *	  Allow stencil count to increment after each cap is rendered.
 *	  Render in a different color if the stencil value indicates
 *	  double coverage.
 *
 *	8 November 1991
 *	Enabled interference checking at all times, not just when
 *	in orange cap mode.
 */

#include <stdio.h>
#include <math.h>
#include <GL/glx.h>
#include <GL/glu.h>

#define INITDEPTH -3.0
#define FOV 45

#define MAXPLANE 6
#define MAXTRANSFORM (MAXPLANE+2)
#define EDIT_VIEW (MAXTRANSFORM-1)
#define EDIT_OBJECT (MAXTRANSFORM-2)
#define MAXMODEL 4

#define STEN_MASK ((1 << stensize) - 1)

void createwindow(void);
int tracking(void);
void limitrot(void);
void reshapewindow(void);
void drawscene(void);
void initgraphics(void);
void setmaterial(int i);
void initparams(void);
void drawcube(long outside, float s);
void drawcylinder(long outside, float s);
void drawsquare(void);
void fillsquare(void);
void setmatrix(long i);
void setinvmat(long i);
void farclip(long b);
void drawobj(void (*func)(), long mat);
void changetitle(void);
void winrect(void);

void bigcube(void);
void middlecube(void);
void littlecube(void);

void bigcylinder(void);
void middlecylinder(void);
void littlecylinder(void);

void longcylinder(void);
void topcylinder(void);
void centercylinder(void);
void bottomcylinder(void);

long xdim,ydim;
float aspect;
int stensize;

float xr[MAXTRANSFORM] = {0,0,0,0,0,0,0,0};
float yr[MAXTRANSFORM] = {0,0,0,0,0,0,0,0};
float depth[MAXTRANSFORM] = {0,0,0,0,0,0,0,INITDEPTH};

float boink_xr;
float boink_yr;
float boink_depth;

long penable[MAXPLANE] = {0,0,0,0,0,0};
long pface[MAXPLANE] = {0,0,0,0,0,0};

float boink_rot_scale;
float boink_depth_scale;
short boink_screen_x;
short boink_screen_y;

int model;
int docap;
int dointersection;
int motion;
int tselect;
int mselect;
int twoside;
int orangecaps;
int ctrlkey = GL_FALSE;
int leftshiftkey = GL_FALSE;
int rightshiftkey = GL_FALSE;
int leftmouse = GL_FALSE;
int middlemouse = GL_FALSE;

Display* dpy;
Window win;
GLXContext cx;

main(int argc, char *argv[]) {
    int i;
    int screen_x, screen_y;
    char s[100];
    XEvent event;
    XButtonEvent* button_event;
    XKeyEvent* key_event;
    XMotionEvent* motion_event;
    XExposeEvent* expose_event;
    XConfigureEvent* config_event;

    /* initialize parameters */
    initparams();

    /* create the window */
    createwindow();

    /* prepare for event loop */
    button_event = (XButtonEvent*)(&event);
    key_event = (XKeyEvent*)(&event);
    motion_event = (XMotionEvent*)(&event);
    expose_event = (XExposeEvent*)(&event);
    config_event = (XConfigureEvent*)(&event);
    XSelectInput(dpy, win, KeyPressMask |
			   ButtonPressMask |
			   ButtonReleaseMask |
			   Button1MotionMask |
			   Button2MotionMask |
			   ExposureMask |
			   StructureNotifyMask);

    /* loop handling events */
    while (1) {
	XNextEvent(dpy, &event);
	initgraphics();
	switch (event.type) {
	    case KeyPress:
		i = XLookupString(key_event, s, 100, NULL, NULL);
		if (i) {
		    switch ((int)(s[0])) {
			case 033:
			    exit(0);
			    break;
			case '0':
			    if (!tracking()) {
				penable[0] = GL_TRUE;
				tselect = 0;
			    }
			    break;
			case ')':	/* shifted zero */
			    penable[0] = GL_FALSE;
			    break;
			case '1':
			    if (!tracking()) {
				penable[1] = GL_TRUE;
				tselect = 1;
			    }
			    break;
			case '!':	/* shifted one */
			    penable[1] = GL_FALSE;
			    break;
			case '2':
			    if (!tracking()) {
				penable[2] = GL_TRUE;
				tselect = 2;
			    }
			    break;
			case '@':	/* shifted two */
			    penable[2] = GL_FALSE;
			    break;
			case '3':
			    if (!tracking()) {
				penable[3] = GL_TRUE;
				tselect = 3;
			    }
			    break;
			case '#':	/* shifted three */
			    penable[3] = GL_FALSE;
			    break;
			case '4':
			    if (!tracking()) {
				penable[4] = GL_TRUE;
				tselect = 4;
			    }
			    break;
			case '$':	/* shifted four */
			    penable[4] = GL_FALSE;
			    break;
			case '5':
			    if (!tracking()) {
				penable[5] = GL_TRUE;
				tselect = 5;
			    }
			    break;
			case '%':	/* shifted five */
			    penable[5] = GL_FALSE;
			    break;
			case 'c':
			    docap = !docap;
			    break;
			case 'd':
			    for (i=0; i<=MAXTRANSFORM; i++)
				printf("xr=%g yr=%g depth=%g\n",
				    xr[i],yr[i],depth[i]);
			    break;
			case 'i':
			    dointersection = !dointersection;
			    break;
			case 'l':
			    twoside = !twoside;
			    if (twoside)
				glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
			    else
				glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
			    break;
			case 'm':
			    motion = !motion;
			    mselect = tselect;
			    break;
			case 'o':
			    if (!tracking())
				tselect = EDIT_OBJECT;
			    break;
			case 'r':
			    initparams();
			    break;
			case 'v':
			    if (!tracking()) {
				tselect = EDIT_VIEW;
			    }
			    break;
			case 'z':
			    orangecaps = !orangecaps;
			    break;
			case ' ':
			    model += 1;
			    if (model >= MAXMODEL)
				model = 0;
			    break;

		    }
		}
		break;
	    case ButtonPress:
		if (button_event->button == 1) {
		    leftmouse = GL_TRUE;
		    boink_screen_x = button_event->x;
		    boink_screen_y = -button_event->y;
		    boink_depth = depth[tselect];
		    boink_depth_scale = 0.002;
		    if (shifted())
			boink_depth_scale /= 10.0;
		    else if (ctrlkey)
			boink_depth_scale /= 100.0;
		}
		else if (button_event->button == 2) {
		    middlemouse = GL_TRUE;
		    boink_screen_x = button_event->x;
		    boink_screen_y = -button_event->y;
		    boink_yr = yr[tselect];
		    boink_xr = xr[tselect];
		    boink_rot_scale = 0.333;
		    if (shifted())
			boink_rot_scale /= 10.0;
		    else if (ctrlkey)
			boink_rot_scale /= 100.0;
		}
		break;
	    case ButtonRelease:
		screen_x = button_event->x;
		screen_y = -button_event->y;
		if (button_event->button == 1) {
		    leftmouse = GL_FALSE;
		    depth[tselect] = boink_depth - boink_depth_scale *
			(float)(boink_screen_x - screen_x);
		}
		else if (button_event->button == 2) {
		    middlemouse = GL_FALSE;
		    yr[tselect] = boink_yr + boink_rot_scale * 
			(float)(boink_screen_x - screen_x);
		    xr[tselect] = boink_xr + boink_rot_scale *
			(float)(boink_screen_y - screen_y);
		    limitrot();
		}
		break;
	    case MotionNotify:
		screen_x = motion_event->x;
		screen_y = -motion_event->y;
		if (leftmouse) {
		    depth[tselect] = boink_depth - boink_depth_scale *
			(float)(boink_screen_x - screen_x);
		}
		else if (middlemouse) {
		    yr[tselect] = boink_yr + boink_rot_scale * 
			(float)(boink_screen_x - screen_x);
		    xr[tselect] = boink_xr + boink_rot_scale *
			(float)(boink_screen_y - screen_y);
		    limitrot();
		}
		break;
	    case Expose:
		break;
	    case ConfigureNotify:
		xdim = config_event->width;
		ydim = config_event->height;
		reshapewindow();
		break;
	}
	if (motion && (!middlemouse || (mselect != tselect))) {
	    while (!XPending(dpy)) {
		yr[mselect] += (float)screen_x / 100;
		xr[mselect] -= (float)screen_y / 100;
		limitrot();
		drawscene();
	    }
	}
	else {
	    if (!XPending(dpy))
		drawscene();
	}
    }
}

int tracking(void) {
    return leftmouse || middlemouse;
}

int shifted(void) {
    return leftshiftkey || rightshiftkey;
}

void limitrot(void) {
    while (xr[tselect] > 360) xr[tselect] -= 360;
    while (xr[tselect] < 0) xr[tselect] += 360;
    while (yr[tselect] > 360) yr[tselect] -= 360;
    while (yr[tselect] < 0) yr[tselect] += 360;
    if (motion && (mselect != tselect)) {
	while (xr[mselect] > 360) xr[mselect] -= 360;
	while (xr[mselect] < 0) xr[mselect] += 360;
	while (yr[mselect] > 360) yr[mselect] -= 360;
	while (yr[mselect] < 0) yr[mselect] += 360;
    }
}

void createwindow(void) {
    XVisualInfo* vi;
    Colormap cmap;
    XSetWindowAttributes swa;
    static int attrib[] = { GLX_RGBA,
			    GLX_DOUBLEBUFFER,
			    GLX_RED_SIZE, 1,
			    GLX_GREEN_SIZE, 1,
			    GLX_BLUE_SIZE, 1,
			    GLX_DEPTH_SIZE, 16,
			    GLX_STENCIL_SIZE, 4,
			    None };

    /* get a connection */
    dpy = XOpenDisplay(0);

    /* get an appropriate visual */
    vi = glXChooseVisual(dpy, DefaultScreen(dpy), attrib);
    if (vi == NULL) {
	fprintf(stderr, "Can't find a satisfactory visual.  Abort.\n");
	exit(1);
    }
    glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &stensize);

    /* create a GLX context */
    cx = glXCreateContext(dpy, vi, 0, GL_TRUE);

    /* create a color map */
    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
                           vi->visual, AllocNone);

    /* create a window */
    swa.colormap = cmap;
    swa.border_pixel = 0;
    swa.event_mask = StructureNotifyMask;
    xdim = 512;
    ydim = 512;
    win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, xdim, ydim,
                        0, vi->depth, InputOutput, vi->visual,
                        CWBorderPixel|CWColormap|CWEventMask, &swa);
    XMapWindow(dpy, win);
}

void reshapewindow(void) {
    glViewport(0, 0, xdim, ydim);
    aspect = (float)xdim / (float)ydim;
    farclip(GL_TRUE);		/* set perspective projection */
    glStencilMask(STEN_MASK);
    glClear(GL_STENCIL_BUFFER_BIT);
}

void farclip(long b) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (b)
	gluPerspective(FOV, aspect, 0.1, 5.0);
    else
	gluPerspective(FOV, aspect, 0.1, 10000.0);
    glMatrixMode(GL_MODELVIEW);
}

void setmatrix(long i) {
    /* do viewing transformation */
    glTranslatef(0.0, 0.0, depth[EDIT_VIEW]);
    glRotatef(-yr[EDIT_VIEW], 0, 1, 0);
    glRotatef(xr[EDIT_VIEW], 1, 0, 0);
    /* do model transformation */
    glRotatef(-yr[i], 0, 1, 0);
    glRotatef(xr[i], 1, 0, 0);
    glTranslatef(0.0, 0.0, depth[i]);
}

void setinvmat(long i) {
    /* compute inverse of model transformation */
    glTranslatef(0.0, 0.0, -depth[i]);
    glRotatef(-xr[i], 1, 0, 0);
    glRotatef(yr[i], 0, 1, 0);
    /* compute inverse of viewing transformation */
    glRotatef(-xr[EDIT_VIEW], 1, 0, 0);
    glRotatef(yr[EDIT_VIEW], 0, 1, 0);
    glTranslatef(0.0, 0.0, -depth[EDIT_VIEW]);
}

GLdouble cp[4] = {0, 0, 1, 0};

void drawscene(void) {
    int i, j;

    /* print errors */
    while (i = glGetError()) {
	fprintf(stderr, "ERROR: 0x%x\n", i);
    }

    /* update title bar */
    changetitle();

    /* clear the screen */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* modify the location of the current clipplane */
    for (i=0; i<MAXPLANE; i++) {
	if ((tselect == i) || (tselect == EDIT_VIEW) ||
		(motion && (mselect == i))) {
	    /* determine which way the clipping plane faces */
	    GLfloat mat[16];
	    glPushMatrix();
	    setinvmat(i);
	    glGetFloatv(GL_MODELVIEW_MATRIX, mat);
	    pface[i] = (mat[14] < 0) ? 1 : 0;
	    glPopMatrix();
	    /* relocate the clipping plane */
	    glPushMatrix();
	    setmatrix(i);
	    glClipPlane(GL_CLIP_PLANE0 + i, cp);
	    glPopMatrix();
	}
    }

    switch (model) {
    case 0:
	drawobj(bigcube,1);
	drawobj(middlecube,3);
	drawobj(littlecube,5);
	break;
    case 1:
	drawobj(bigcylinder,1);
	drawobj(middlecylinder,3);
	drawobj(littlecylinder,5);
	break;
    case 2:
	drawobj(topcylinder,3);
	drawobj(centercylinder,4);
	drawobj(bottomcylinder,5);
	drawobj(longcylinder,1);
	break;
    case 3:
	drawobj(bigcube,1);
	drawobj(middlecube,3);
	drawobj(littlecube,5);
	drawobj(bigcylinder,1);
	drawobj(middlecylinder,3);
	drawobj(littlecylinder,5);
	break;
    }

    /* disable all clipping planes */
    for (i=0; i<MAXPLANE; i++) {
	glDisable(GL_CLIP_PLANE0 + i);
    }

    /* draw intersections and clear stencil buffer */
    if (dointersection) {
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_LESS, 2, STEN_MASK);
	glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
	glDepthFunc(GL_ALWAYS);
	glColor4f(1, 1, 0, 1);
	winrect();
	glDepthFunc(GL_GEQUAL);
	glDisable(GL_STENCIL_TEST);
    } else
	glClear(GL_STENCIL_BUFFER_BIT);

    /* draw the current clipping plane */
    if ((tselect < MAXPLANE) && penable[tselect]) {
	if (pface[tselect])
	    glColor4f(1, 1, 1, 1);
	else
	    glColor4f(0, 1, 0, 1);
	glPushMatrix();
	setmatrix(tselect);
	drawsquare();
	glPopMatrix();
    }

    glXSwapBuffers(dpy, win);
}

void drawobj(void (*func)(), long mat) {
    register i;

    /* setup the matrix for drawing the object */
    glPushMatrix(); 
    setmatrix(EDIT_OBJECT);

    /* enable selected clipping planes - draw object */
    for (i=0; i<MAXPLANE; i++) {
	if (penable[i])
	    glEnable(GL_CLIP_PLANE0 + i);
	else
	    glDisable(GL_CLIP_PLANE0 + i);
    }
    if (docap)
	glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
    setmaterial(mat);
    (*func)();
    glDisable(GL_LIGHTING);
    if (docap)
	glDisable(GL_CULL_FACE);

    /* done if capping is not enabled */
    if (!docap) {
	glPopMatrix();
	return;
    }

    /* enable only front clipping planes - draw into stencil bitplanes */
    for (i=0; i<MAXPLANE; i++) {
	if (penable[i] && pface[i])
	    glEnable(GL_CLIP_PLANE0 + i);
	else
	    glDisable(GL_CLIP_PLANE0 + i);
    }
    farclip(GL_FALSE);
    glDisable(GL_DEPTH_TEST);
    glColorMask(0, 0, 0, 0);
    glStencilMask(0x01);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0, 0x1);
    glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
    (*func)();
    farclip(GL_TRUE);
    glEnable(GL_DEPTH_TEST);
    glColorMask(1, 1, 1, 1);
    glStencilMask(STEN_MASK);
    glPopMatrix();

    /* draw caps */
    for (i=0; i<MAXPLANE; i++) {
	if (penable[i])
	    glEnable(GL_CLIP_PLANE0 + i);
	else
	    glDisable(GL_CLIP_PLANE0 + i);
    }
    glDepthFunc(GL_ALWAYS);
    glStencilFunc(GL_EQUAL, 1, 0x1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
    for (i=0; i<MAXPLANE; i++) {
	if (penable[i] && pface[i]) {
	    glDisable(GL_CLIP_PLANE0 + i);
	    glPushMatrix();
	    setmatrix(i);
	    if (orangecaps) {
		glColor4f(1, 0.5, 0, 1);
		fillsquare();
	    } else {
		glEnable(GL_LIGHTING);
		setmaterial(mat);
		fillsquare();
		glDisable(GL_LIGHTING);
	    }
	    glPopMatrix();
	    glEnable(GL_CLIP_PLANE0 + i);
	}
    }

    glDepthFunc(GL_GEQUAL);
    glDisable(GL_STENCIL_TEST);
}

void bigcube(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcube(1, 0.5);
	drawcube(0, 0.45);
	glEndList();
    }
}

void middlecube(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcube(1, 0.3);
	drawcube(0, 0.25);
	glEndList();
    }
}

void littlecube(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcube(1, 0.1);
	drawcube(0, 0.05);
	glEndList();
    }
}

void bigcylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcylinder(1, 0.5);
	drawcylinder(0, 0.45);
	glEndList();
    }
}

void middlecylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcylinder(1, 0.3);
	drawcylinder(0, 0.25);
	glEndList();
    }
}

void littlecylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcylinder(1, 0.1);
	drawcylinder(0, 0.05);
	glEndList();
    }
}

void longcylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	drawcylinder(1, 0.5);
	glEndList();
    }
}

void topcylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	glPushMatrix();
	glTranslatef(0.0, 0.0, 0.4);
	glRotatef(90, 1, 0, 0);
	drawcylinder(1, 0.3);
	glPopMatrix();
	glEndList();
    }
}

void centercylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	glPushMatrix();
	glRotatef(90, 1, 0, 0);
	glRotatef(45, 0, 1, 0);
	drawcylinder(1, 0.3);
	glPopMatrix();
	glEndList();
    }
}

void bottomcylinder(void) {
    static GLuint list = 0;

    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	glPushMatrix();
	glTranslatef(0.0, 0.0, -0.4);
	glRotatef(90, 1, 0, 0);
	glRotatef(90, 0, 1, 0);
	drawcylinder(1, 0.3);
	glPopMatrix();
	glEndList();
    }
}

GLfloat cubevertex[8][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},
};

GLfloat cubenormal[6][4] = {
    { 0.0,  0.0, -1.0},
    { 1.0,  0.0,  0.0},
    { 0.0,  0.0,  1.0},
    {-1.0,  0.0,  0.0},
    { 0.0,  1.0,  0.0},
    { 0.0, -1.0,  0.0},
};

void drawcube(long outside, float s) {
    int i;
    GLfloat cv[8][4];

    for (i=0; i<8; i++) {
	cv[i][0] = s * cubevertex[i][0];
	cv[i][1] = s * cubevertex[i][1];
	cv[i][2] = s * cubevertex[i][2];
	cv[i][3] = 1;
    }

    glBegin(GL_QUADS);
    if (outside) {
	glNormal3fv(cubenormal[0]);
	glVertex3fv(cv[2]);
	glVertex3fv(cv[3]);
	glVertex3fv(cv[1]);
	glVertex3fv(cv[0]);

	glNormal3fv(cubenormal[1]);
	glVertex3fv(cv[3]);
	glVertex3fv(cv[7]);
	glVertex3fv(cv[5]);
	glVertex3fv(cv[1]);

	glNormal3fv(cubenormal[2]);
	glVertex3fv(cv[7]);
	glVertex3fv(cv[6]);
	glVertex3fv(cv[4]);
	glVertex3fv(cv[5]);

	glNormal3fv(cubenormal[3]);
	glVertex3fv(cv[6]);
	glVertex3fv(cv[2]);
	glVertex3fv(cv[0]);
	glVertex3fv(cv[4]);

	glNormal3fv(cubenormal[4]);
	glVertex3fv(cv[6]);
	glVertex3fv(cv[7]);
	glVertex3fv(cv[3]);
	glVertex3fv(cv[2]);

	glNormal3fv(cubenormal[5]);
	glVertex3fv(cv[0]);
	glVertex3fv(cv[1]);
	glVertex3fv(cv[5]);
	glVertex3fv(cv[4]);
    }
    else {
	glNormal3fv(cubenormal[2]);
	glVertex3fv(cv[0]);
	glVertex3fv(cv[1]);
	glVertex3fv(cv[3]);
	glVertex3fv(cv[2]);

	glNormal3fv(cubenormal[3]);
	glVertex3fv(cv[1]);
	glVertex3fv(cv[5]);
	glVertex3fv(cv[7]);
	glVertex3fv(cv[3]);

	glNormal3fv(cubenormal[0]);
	glVertex3fv(cv[5]);
	glVertex3fv(cv[4]);
	glVertex3fv(cv[6]);
	glVertex3fv(cv[7]);

	glNormal3fv(cubenormal[1]);
	glVertex3fv(cv[4]);
	glVertex3fv(cv[0]);
	glVertex3fv(cv[2]);
	glVertex3fv(cv[6]);

	glNormal3fv(cubenormal[5]);
	glVertex3fv(cv[2]);
	glVertex3fv(cv[3]);
	glVertex3fv(cv[7]);
	glVertex3fv(cv[6]);

	glNormal3fv(cubenormal[4]);
	glVertex3fv(cv[4]);
	glVertex3fv(cv[5]);
	glVertex3fv(cv[1]);
	glVertex3fv(cv[0]);
    }
    glEnd();
}

#define SLICES 80
#define LENGTH 1
#define WIDTH 0.5

void drawcylinder(long outside, float s) {
    static int init = GL_FALSE;
    static GLUquadricObj* inobj = 0;
    static GLUquadricObj* outobj = 0;

    if (!init) {
	init = GL_TRUE;
	inobj = gluNewQuadric();
	gluQuadricOrientation(inobj, GLU_INSIDE);
	outobj = gluNewQuadric();
	gluQuadricOrientation(outobj, GLU_OUTSIDE);
    }

    glPushMatrix();
    glTranslatef(0, 0, -s*LENGTH);
    gluCylinder(outside?outobj:inobj, s*WIDTH, s*WIDTH, s*2*LENGTH, SLICES, 1);
    gluDisk(outside?inobj:outobj, 0, s*WIDTH, SLICES, 1);
    glTranslatef(0, 0, s*2*LENGTH);
    gluDisk(outside?outobj:inobj, 0, s*WIDTH, SLICES, 1);
    glPopMatrix();
}

void drawsquare(void) {
    glEnable(GL_BLEND);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glRecti(-1, -1, 1, 1);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_BLEND);
}

void fillsquare(void) {
    glNormal3f(0, 0, 1);
    glRecti(-100, -100, 100, 100);
}

void changetitle(void) {
    static char oldtitle[100] = "";
    char title[100];
    if (tselect < MAXPLANE)
	sprintf(title,"cap: move plane %d",tselect);
    else if (tselect == EDIT_OBJECT)
	sprintf(title,"cap: move object");
    else if (tselect == EDIT_VIEW)
	sprintf(title,"cap: move view");
    else
	sprintf(title,"cap:");
    if (strcmp(title,oldtitle)) {
	strcpy(oldtitle,title);
	XStoreName(dpy, win, title);
    }
}

void initparams(void) {
    int i;
    for (i=0; i<MAXPLANE; i++)
	penable[i] = GL_FALSE;
    for (i=0; i<MAXTRANSFORM; i++) {
	xr[i] = 0.0;
	yr[i] = 0.0;
	depth[i] = 0.0;
    }
    penable[0] = GL_TRUE;
    depth[EDIT_VIEW] = INITDEPTH;
    tselect = 0;
    mselect = EDIT_VIEW;
    motion = GL_FALSE;
    docap = GL_TRUE;
    dointersection = GL_TRUE;
    twoside = GL_TRUE;
    model = 0;
    orangecaps = GL_FALSE;
}

static GLfloat* fvect4(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {
    static GLfloat f[4];
    f[0] = r;
    f[1] = g;
    f[2] = b;
    f[3] = a;
    return f;
}

void initgraphics(void) {
    static int done = GL_FALSE;
    if (!done) {
	done = GL_TRUE;
	glXMakeCurrent(dpy, win, cx);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_GEQUAL);
	glDepthRange(1, 0);
	glLightfv(GL_LIGHT0, GL_AMBIENT, fvect4(0, 0, 0, 1));
	glLightfv(GL_LIGHT0, GL_DIFFUSE, fvect4(1, 1, 1, 1));
	glLightfv(GL_LIGHT0, GL_SPECULAR, fvect4(1, 1, 1, 1));
	glLightfv(GL_LIGHT0, GL_POSITION, fvect4(0, 0, 1, 0));
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fvect4(.3,.3,.3,1));
	if (twoside)
	    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
	glEnable(GL_LIGHT0);

	glClearColor(0, 0, 0, 0);
	glClearDepth(0);
	glClearStencil(0);
	glClear(GL_STENCIL_BUFFER_BIT);
	glEnable(GL_LINE_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }
}

#define GL_FANDB GL_FRONT_AND_BACK

void setmaterial(int i) {
    static GLuint lists[5] = {0, 0, 0, 0, 0};
    GLuint list;

    list = lists[i];
    if (list)
	glCallList(list);
    else {
	list = glGenLists(1);
	lists[i] = list;
	glNewList(list, GL_COMPILE_AND_EXECUTE);
	switch (i) {
	    case 1:
		glMaterialfv(GL_FANDB, GL_AMBIENT, fvect4(.65,.5,.35,1));
		glMaterialfv(GL_FANDB, GL_DIFFUSE, fvect4(.65,.5,.35,1));
		glMaterialfv(GL_FANDB, GL_SPECULAR, fvect4(0,0,0,1));
		glMaterialf(GL_FANDB, GL_SHININESS, 5);
		break;
	    case 2:
		glMaterialfv(GL_FANDB, GL_AMBIENT, fvect4(.6,.55,.65,1));
		glMaterialfv(GL_FANDB, GL_DIFFUSE, fvect4(.6,.55,.65,1));
		glMaterialfv(GL_FANDB, GL_SPECULAR, fvect4(.9,.9,.95,1));
		glMaterialf(GL_FANDB, GL_SHININESS, 10);
		break;
	    case 3:
		glMaterialfv(GL_FANDB, GL_AMBIENT, fvect4(.5,.1,.1,1));
		glMaterialfv(GL_FANDB, GL_DIFFUSE, fvect4(.5,.1,.1,1));
		glMaterialfv(GL_FANDB, GL_SPECULAR, fvect4(.45,.45,.45,1));
		glMaterialf(GL_FANDB, GL_SHININESS, 30);
		break;
	    case 4:
		glMaterialfv(GL_FANDB, GL_AMBIENT, fvect4(.1,.5,.1,1));
		glMaterialfv(GL_FANDB, GL_DIFFUSE, fvect4(.1,.5,.1,1));
		glMaterialfv(GL_FANDB, GL_SPECULAR, fvect4(.45,.45,.45,1));
		glMaterialf(GL_FANDB, GL_SHININESS, 30);
		break;
	    case 5:
		glMaterialfv(GL_FANDB, GL_AMBIENT, fvect4(.1,.1,.5,1));
		glMaterialfv(GL_FANDB, GL_DIFFUSE, fvect4(.1,.1,.5,1));
		glMaterialfv(GL_FANDB, GL_SPECULAR, fvect4(.45,.45,.45,1));
		glMaterialf(GL_FANDB, GL_SHININESS, 30);
		break;
	    default:
		fprintf(stderr, "Undefined color index %d.  Abort.\n", i);
		exit(1);
	}
	glEndList();
    }
}

void winrect(void) {
    /* draw a rectangle the size of the window */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 1, 0, 1, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glRectf(0, 0, 1, 1);
    glPopMatrix();
    farclip(GL_TRUE);
}


