#include #include #include #include #include #include #include #include #include #include #include "3darith.h" #include "mathutils.h" #define WINX 400 #define WINY 400 #define MZX 10 #define MZY 10 #define TEXSIZE 512 extern const char *__progname; static Display *disp; static XVisualInfo *vi; static GLXContext ctx; static Screen *scr; static int scrwidth; static int scrheight; static int depth; static Window rootwin; static Colormap cmap; static Window win; static XColor bgcolour; static XColor fgcolour; static GC gc; static struct timeval nexttick; static unsigned int tex; static unsigned char texture[TEXSIZE+2][TEXSIZE+2][4]; /* * It would be conceptually easier to keep this state as the camera's * unit vectors (eg, forward, up, and right) as measured in world * coordinates. However, if we do that, we have to invert a * transformation matrix at rendering time. To make the render-time * operations easy, we have to instead maintain the converse: the * world basis vectors in eye coordinates. These are wx, wy, and wz: * the world x, y, and z unit vectors, in eye coordinates. However, * in order to make motion feasibly simple, we also maintain the * converse: the eye x, y, and z unit vectors in world coordinates. * * We could simply maintain them separately. But then numerical * roundoff errors would cause the two sets, theoretically inverses of * one another, to drift out of sync. So, instead, we depend on * another property: rotation and movement are usually well-separated, * meaning it is feasible to maintain only one of them and invert the * matrix when the other is needed. Since we constantly need wx/wy/wz * for rendering, it is ex/ey/ez that we maintain lazily. evalid is a * boolean indicating whether ex/ey/ez are valid. * * wx/wy/wz and ex/ey/ez represent only rotations. The player's * location in world coorindates is stored in ploc. Like wx/wy/wz, * this is always valid. * * "eye coordinates" are a coordinate system whose Z axis points * opposite the view direction, whose X axis points in the camera's * "right" direction, and whose Y axis points in the camera's "up" * direction. The Z axis is negated from the obvious convention * because that's required by the combination of two other useful * properties: (1) eye X and Y coordinates are "the same as" (parallel * to and same sign as) screen X and Y coordinates and (2) the eye * coordinate system obeys the same right-hand rule as the world * coordinate system. */ static XYZ wx; static XYZ wy; static XYZ wz; static XYZ ploc; static int evalid; static XYZ ex; static XYZ ey; static XYZ ez; static unsigned int kbstate; #define KBS_ML 0x00000001 #define KBS_MR 0x00000002 #define KBS_RL 0x00000004 #define KBS_RR 0x00000008 #define KBS_MF 0x00000010 #define KBS_MB 0x00000020 #define KBS_LSHF 0x00000040 #define KBS_RSHF 0x00000080 #define KBS_LCTL 0x00000100 #define KBS_RCTL 0x00000200 #define KBS_SHIFT (KBS_LSHF|KBS_RSHF) #define KBS_CTRL (KBS_LCTL|KBS_RCTL) static int dbg; static int swapped; typedef unsigned char MZBITS; static MZBITS maze[MZX][MZY]; #define MZ_PX 0x01 #define MZ_MX 0x02 #define MZ_PY 0x04 #define MZ_MY 0x08 #define MZ_F 0x10 #define MZ_S 0x20 #define MZ_G 0x40 static unsigned int ticks; static GLuint mazelist; static int sx; static int sy; static int gx; static int gy; static void open_display(void) { disp = XOpenDisplay(0); if (disp == 0) { fprintf(stderr,"%s: can't open display\n",__progname); exit(1); } } static void find_visual(void) { XVisualInfo template; int nvi; template.visualid = 35; vi = XGetVisualInfo(disp,VisualIDMask,&template,&nvi); if (nvi < 1) { fprintf(stderr,"%s: can't find visual\n",__progname); exit(1); } if (nvi > 1) { fprintf(stderr,"%s: found multiple visuals\n",__progname); exit(1); } } static void setup_X(void) { Pixmap p; scr = XScreenOfDisplay(disp,vi->screen); scrwidth = XWidthOfScreen(scr); scrheight = XHeightOfScreen(scr); depth = vi->depth; rootwin = XRootWindowOfScreen(scr); cmap = XCreateColormap(disp,rootwin,vi->visual,AllocNone); XParseColor(disp,cmap,"#646",&bgcolour); XAllocColor(disp,cmap,&bgcolour); XParseColor(disp,cmap,"#fff",&fgcolour); XAllocColor(disp,cmap,&fgcolour); p = XCreatePixmap(disp,rootwin,1,1,depth); gc = XCreateGC(disp,p,0,0); XFreePixmap(disp,p); } static void setup_context(void) { ctx = glXCreateContext(disp,vi,0,True); if (! ctx) { fprintf(stderr,"%s: can't create GL context\n",__progname); exit(1); } } static int clip(int, int, int) __attribute__((__const__)); static int clip(int v, int min, int max) { return((vmax)?max:v); } static double blend(double, double, double) __attribute__((__const__)); static double blend(double v1, double a, double v2) { return((a*v2)+((1-a)*v1)); } static void setup_bitmaps(void) { int x; int y; double fx; double fy; for (y=TEXSIZE+2-1;y>=0;y--) { fy = (y - 1) / (double)TEXSIZE; for (x=TEXSIZE+2-1;x>=0;x--) { fx = ((x - 1) * 3) / (double)TEXSIZE; if ((x == 0) || (x == TEXSIZE+2-1) || (y == 0) || (y == TEXSIZE+2-1)) { texture[y][x][0] = 127; texture[y][x][1] = 127; texture[y][x][2] = 127; texture[y][x][3] = 255; } else { texture[y][x][0] = clip(blend(127.5,fy,(fx<1)?fx*256:0),0,255); texture[y][x][1] = clip(blend(127.5,fy,((fx>=1)&&(fx<2))?(fx-1)*256:0),0,255); texture[y][x][2] = clip(blend(127.5,fy,(fx>=2)?(fx-2)*256:0),0,255); texture[y][x][3] = 255; } } } } static void create_window(void) { XSetWindowAttributes attr; unsigned long int attrmask; attrmask = 0; attr.background_pixel = bgcolour.pixel; attrmask |= CWBackPixel; attr.event_mask = StructureNotifyMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask; attrmask |= CWEventMask; attr.colormap = cmap; attrmask |= CWColormap; win = XCreateWindow(disp,rootwin,0,0,WINX*2,WINY,0,depth,InputOutput,vi->visual,attrmask,&attr); XMapRaised(disp,win); } static void setup_gl(void) { double ps[4] = { .1, 0, 0, 0 }; double pt[4] = { 0, .1, 0, 0 }; if (glXMakeCurrent(disp,(GLXDrawable)win,ctx) != True) { fprintf(stderr,"%s: can't make context current\n",__progname); exit(1); } glShadeModel(GL_SMOOTH); glClearColor(0,0,0,0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-.25,.25,-.125,.125,.25,(hypot(MZX,MZY)+1)*10); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); glEnable(GL_POLYGON_OFFSET_FILL); glPixelStorei(GL_UNPACK_ALIGNMENT,1); glGenTextures(1,&tex); glBindTexture(GL_TEXTURE_2D,tex); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,TEXSIZE+2,TEXSIZE+2,1,GL_RGBA,GL_UNSIGNED_BYTE,&texture[0][0][0]); glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR); glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR); glTexGendv(GL_S,GL_EYE_PLANE,&ps[0]); glTexGendv(GL_T,GL_EYE_PLANE,&pt[0]); glCullFace(GL_BACK); glFrontFace(GL_CW); glEnable(GL_CULL_FACE); kbstate = 0; } static void setup_model(void) { int x; int y; typedef struct { unsigned char x; unsigned char y; MZBITS bit; } PEND; PEND list[MZX*MZY*4]; PEND p; int listlen; int i; MZBITS obit; void add_list(int x, int y, MZBITS bit) { list[listlen++] = (PEND) { .x = x, .y = y, .bit = bit }; } void add_neighbours(int x, int y) { if (x > 0) add_list(x,y,MZ_MX); if (y > 0) add_list(x,y,MZ_MY); if (x < MZX-1) add_list(x,y,MZ_PX); if (y < MZY-1) add_list(x,y,MZ_PY); } for (x=MZX-1;x>=0;x--) for (y=MZY-1;y>=0;y--) maze[x][y] = 0; srandom(time(0)); maze[0][0] = MZ_S | MZ_F; listlen = 0; add_neighbours(0,0); while (listlen > 0) { i = random() % listlen; p = list[i]; listlen --; if (i != listlen) list[i] = list[listlen]; x = p.x; y = p.y; switch (p.bit) { case MZ_PX: x ++; obit = MZ_MX; break; case MZ_MX: x --; obit = MZ_PX; break; case MZ_PY: y ++; obit = MZ_MY; break; case MZ_MY: y --; obit = MZ_PY; break; default: abort(); break; } if (maze[x][y] & MZ_F) continue; maze[p.x][p.y] |= p.bit; maze[x][y] = obit | MZ_F; add_neighbours(x,y); } maze[MZX-1][MZY-1] |= MZ_G; for (x=MZX-1;x>=0;x--) for (y=MZY-1;y>=0;y--) { if (! (maze[x][y] & MZ_F)) abort(); if ( ( (x > 0) && ( ( (maze[x-1][y] & MZ_PX) && !(maze[x][y] & MZ_MX) ) || ( !(maze[x-1][y] & MZ_PX) && (maze[x][y] & MZ_MX) ) ) ) || ( (y > 0) && ( ( (maze[x][y-1] & MZ_PY) && !(maze[x][y] & MZ_MY) ) || ( !(maze[x][y-1] & MZ_PY) && (maze[x][y] & MZ_MY) ) ) ) ) abort(); } for (x=MZX;x>0;x--) printf("+---"); printf("+\n"); for (y=MZY-1;y>=0;y--) { for (x=0;x= MZX) x = MZX - 1; if (y < 0) y = 0; else if (y >= MZY) y = MZY - 1; bits = maze[x][y]; x *= 10; y *= 10; if (!(bits & MZ_PX) && (nploc.x > x+9)) nploc.x = x + 9; if (!(bits & MZ_MX) && (nploc.x < x+2)) nploc.x = x + 2; if (!(bits & MZ_PY) && (nploc.y > y+9)) nploc.y = y + 9; if (!(bits & MZ_MY) && (nploc.y < y+2)) nploc.y = y + 2; if (nploc.x < x+5) px = x + .5; else px = x + 10.5; if (nploc.y < y+5) py = y + .5; else py = y + 10.5; if ((fabs(nploc.x-px) <= 1.5) && (fabs(nploc.y-py) <= 1.5)) { if (fabs(nploc.x-px) <= .5) { if ((nploc.y > py) && (nploc.y < py+1.5)) nploc.y = py + 1.5; else if ((nploc.y < py) && (nploc.y > py-1.5)) nploc.y = py - 1.5; } else if (fabs(nploc.y-py) <= .5) { if ((nploc.x > px) && (nploc.x < px+1.5)) nploc.x = px + 1.5; else if ((nploc.x < px) && (nploc.x > px-1.5)) nploc.x = px - 1.5; } else { if (nploc.x < px) px -= .5; else px += .5; if (nploc.y < py) py -= .5; else py += .5; d = hypot(px-nploc.x,py-nploc.y); if (d < 1) { nploc.x = px - ((px - nploc.x) / d); nploc.y = py - ((py - nploc.y) / d); } } } ploc = nploc; } static void motion(void) { double f; if (kbstate & KBS_SHIFT) f = 25; else if (kbstate & KBS_CTRL) f = 1; else f = 5; f *= .3; switch (kbstate & (KBS_ML|KBS_MR)) { case KBS_ML: ensure_e(); moveby(ex,-f); break; case KBS_MR: ensure_e(); moveby(ex,f); break; } switch (kbstate & (KBS_MF|KBS_MB)) { case KBS_MF: ensure_e(); moveby(ez,-f); break; case KBS_MB: ensure_e(); moveby(ez,f); break; } switch (kbstate & (KBS_RL|KBS_RR)) { case KBS_RL: rotate_world(-f,(XYZ){0,1,0}); break; case KBS_RR: rotate_world(f,(XYZ){0,1,0}); break; } } static void keystroke(XKeyEvent *ev, char updn) { KeySym ks; unsigned int bit; ks = XLookupKeysym(ev,0); bit = 0; switch (ks) { case XK_w: case XK_W: bit = KBS_MF; break; case XK_s: case XK_S: bit = KBS_MB; break; case XK_a: case XK_A: bit = KBS_ML; break; case XK_d: case XK_D: bit = KBS_MR; break; case XK_u: case XK_U: bit = KBS_RL; break; case XK_i: case XK_I: bit = KBS_RR; break; case XK_Shift_L: bit = KBS_LSHF; break; case XK_Shift_R: bit = KBS_RSHF; break; case XK_Control_L: bit = KBS_LCTL; break; case XK_Control_R: bit = KBS_RCTL; break; case XK_x: case XK_X: if (updn == 'd') swapped = ! swapped; break; case XK_q: case XK_Q: if ((updn == 'u') && (kbstate & KBS_SHIFT)) exit(0); break; case XK_p: case XK_P: if (updn == 'd') dbg = 1; break; } switch (updn) { case 'u': kbstate &= ~bit; break; case 'd': kbstate |= bit; break; default: abort(); break; } } static void events(void) { XEvent ev; while (XPending(disp)) { XNextEvent(disp,&ev); switch (ev.type) { default: /* XXX ignore */ break; case KeyPress: /* XKeyPressedEvent - XKeyEvent - xkey */ keystroke(&ev.xkey,'d'); break; case KeyRelease: /* XKeyReleasedEvent - XKeyEvent - xkey */ keystroke(&ev.xkey,'u'); break; case MappingNotify: /* XMappingEvent - xmapping */ XRefreshKeyboardMapping(&ev.xmapping); break; } } } static void await(void) { struct timeval now; int ms; nexttick.tv_usec += 75000; if (nexttick.tv_usec >= 1000000) { nexttick.tv_usec -= 1000000; nexttick.tv_sec ++; } while (1) { gettimeofday(&now,0); if ( (now.tv_sec > nexttick.tv_sec) || ( (now.tv_sec == nexttick.tv_sec) && (now.tv_usec >= nexttick.tv_usec) ) ) return; ms = ((nexttick.tv_sec - now.tv_sec) * 1000) + 1 + (nexttick.tv_usec / 1000) - (now.tv_usec / 1000); if (ms < 1) ms = 1; poll(0,0,ms); } } static void tick(void) { motion(); render(); events(); await(); ticks ++; } static void setup_view(void) { ploc = (XYZ){2,2,0}; ey = (XYZ){0,0,1}; ez = xyzunit((XYZ){-1,-1,0}); ex = xyzcross(ey,ez); invert_rotations(ex,ey,ez,&wx,&wy,&wz); evalid = 1; swapped = 0; } static void setup_input(void) { XGrabKeyboard(disp,win,False,GrabModeAsync,GrabModeAsync,CurrentTime); } static void setup_lists(void) { int x; int y; mazelist = glGenLists(1); glNewList(mazelist,GL_COMPILE); glPolygonOffset(0,1); sx = -1; gx = -1; /* walls */ glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,tex); glBegin(GL_QUADS); for (x=MZX-1;x>=0;x--) for (y=MZY-1;y>=0;y--) { if (! (maze[x][y] & MZ_PX)) draw_wall((x+1)*10,(y*10)+1,0,9); if (! (maze[x][y] & MZ_MX)) draw_wall((x*10)+1,(y+1)*10,0,-9); if (! (maze[x][y] & MZ_PY)) draw_wall((x+1)*10,(y+1)*10,-9,0); if (! (maze[x][y] & MZ_MY)) draw_wall((x*10)+1,(y*10)+1,9,0); if (maze[x][y] & MZ_S) { sx = x * 10; sy = y * 10; } if (maze[x][y] & MZ_G) { gx = x * 10; gy = y * 10; } } glEnd(); glDisable(GL_TEXTURE_2D); glBegin(GL_QUADS); /* floor */ glColor3f(.333,.333,.333); glVertex3d(0,0,-1); glVertex3d(0,(MZY*10)+1,-1); glVertex3d((MZX*10)+1,(MZY*10)+1,-1); glVertex3d((MZX*10)+1,0,-1); /* ceiling */ glColor3f(.667,.667,.667); glVertex3d(0,0,1); glVertex3d((MZX*10)+1,0,1); glVertex3d((MZX*10)+1,(MZY*10)+1,1); glVertex3d(0,(MZY*10)+1,1); /* start marker */ if (sx < 0) abort(); glColor3f(.333,.333,.667); glVertex3d(sx+1,sy+1,-.99); glVertex3d(sx+1,sy+10,-.99); glVertex3d(sx+10,sy+10,-.99); glVertex3d(sx+10,sy+1,-.99); /* goal marker, top cover (sides are done dynamically - they flash) */ if (gx < 0) abort(); glColor3f(.333,1,.333); glVertex3d(gx+4,gy+4,-.5); glVertex3d(gx+4,gy+7,-.5); glVertex3d(gx+7,gy+7,-.5); glVertex3d(gx+7,gy+4,-.5); glEnd(); /* "structural" posts */ glColor3f(.5,.5,.5); for (x=MZX;x>=0;x--) for (y=MZY;y>=0;y--) { glBegin(GL_QUAD_STRIP); glVertex3d(x*10,y*10,-1); glVertex3d(x*10,y*10,1); glVertex3d((x*10)+1,y*10,-1); glVertex3d((x*10)+1,y*10,1); glVertex3d((x*10)+1,(y*10)+1,-1); glVertex3d((x*10)+1,(y*10)+1,1); glVertex3d(x*10,(y*10)+1,-1); glVertex3d(x*10,(y*10)+1,1); glVertex3d(x*10,y*10,-1); glVertex3d(x*10,y*10,1); glEnd(); } glEndList(); } int main(void); int main(void) { open_display(); find_visual(); setup_X(); setup_context(); setup_bitmaps(); create_window(); setup_gl(); setup_input(); setup_model(); setup_view(); setup_ticks(); setup_lists(); while (1) tick(); }