/* * Maze: * * 15924 [6] [x] = switch for x * | * 26724--6--26723 26722--5--26721 * / | | | / | * 26700-26--26699-51--26698 * 26694 2 *=goal * | 5 | / | | * 26720-----26719 26715 2 26713-----26558 [7] * | | | / | * 26201 3 26692 1 26689 26683-----26405 [5] * | | 1 | | | * 3 26495-----26712 7 26711 4 26710 * | / | | | / | | / | * in -> 9923 1 26679--3--26657 4 26653 3 * | | | 3 | * 1 26704--2--26703 26702--1--26701 * | / / | * 26279--2--26652-----26650 26642 * * The "in" room is the antechamber at the top of the stairs; below * that is the lobby and the slide destination room. */ #include #include #include #include #include #include #include #include #include #include #include #include "3darith.h" #include "findvis.h" #include "mathutils.h" #define WINX 400 #define WINY 400 #define PLAYER_SIDES 10 #define PLAYER_SIZE .1 #define PLAYER_HEIGHT .25 #define PLAYER_PEAK .3 #define CAMERA_SIZE .05 #define MAXMOVE 1 #define CAMERA_ADJUST .5 extern const char *__progname; typedef enum { TT_POINT = 1, TT_BOX } TRIGTYPE; typedef struct room ROOM; typedef struct roomlist ROOMLIST; typedef struct trig TRIG; typedef struct stair STAIR; struct stair { ROOM *rv[20]; TRIG *gd[20]; TRIG *gu[20]; double x1; double y1; double tread; double width; double floor0; double floor1; } ; struct roomlist { ROOMLIST *link; ROOM *room; } ; struct trig { TRIG *link; const char *text; TRIGTYPE type; unsigned int flags; #define TRIGF_ACTIVE 0x00000001 union { struct { XYZ pt; double radius; double mintheta; double maxtheta; } point; struct { XYZ x; XYZ y; double xmin; double xmax; double ymin; double ymax; } box; } ; void (*action)(void *); void *arg; } ; struct room { const char *text; TRIG *ptriggers; TRIG *ctriggers; ROOMLIST *otherdisp; GLuint list; double floorz; } ; 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 frameus; static struct timeval now; static unsigned int walltex; #define WALLTEXSIZE 512 #define WALLTEXSUB 8 static unsigned char walltexinit[WALLTEXSIZE*WALLTEXSIZE] = { #include "walltexture.inc" }; static unsigned char walltexture[WALLTEXSIZE+2][WALLTEXSIZE+2][4]; /* * We maintain both the eye unit vectors in world coordinates (ex, ey, * ez) and the world unit vectors in eye coordinates (wx, wy, wz); * whenever we change either, we recompute the other by inverting a * 3x3 matrix (which, since they are rotation matrices, can be easily * done closed-form). wx/wy/wz and ex/ey/ez represent only rotations. * The player's location in world coordinates is stored in ploc, the * camera's in cloc. * * "eye coordinates" are a coordinate system whose Z axis points * opposite the view direction ("towards the camera"), 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 XYZ cloc; 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 unsigned int ticks; static unsigned long int seed; static ROOM *lobby; static GLuint playerlist; static ROOM *curroom; static unsigned int dbg = 0; #define DBG_STATE 0x00000001 #define DBG_FIRING 0x00000002 #define DBG_GATE 0x00000004 #define DEGTORAD (M_PI / 180) #define RADTODEG (180 / M_PI) static void open_display(void) { disp = XOpenDisplay(0); if (disp == 0) { fprintf(stderr,"%s: can't open display\n",__progname); exit(1); } } static void setup_random(void) { srandom(time(0)); } static void setup_visual(void) { vi = find_visual(disp); } 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); } } #if 0 static int clip(int, int, int) __attribute__((__const__)); static int clip(int v, int min, int max) { return((vmax)?max:v); } #endif #if 0 static double blend(double, double, double) __attribute__((__const__)); static double blend(double v1, double a, double v2) { return((a*v2)+((1-a)*v1)); } #endif static void setup_textures(void) { int x; int y; int i; int v; i = 0; for (y=WALLTEXSIZE+2-1;y>=0;y--) for (x=WALLTEXSIZE+2-1;x>=0;x--) { if ((x == 0) || (x == WALLTEXSIZE+2-1) || (y == 0) || (y == WALLTEXSIZE+2-1)) { walltexture[y][x][0] = 127; walltexture[y][x][1] = 127; walltexture[y][x][2] = 127; } else { v = walltexinit[i++]; walltexture[y][x][0] = v; walltexture[y][x][1] = v; walltexture[y][x][2] = v; } walltexture[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(-.05,.05,-.025,.025,.05,100); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); glEnable(GL_POLYGON_OFFSET_FILL); glPixelStorei(GL_UNPACK_ALIGNMENT,1); glGenTextures(1,&walltex); glBindTexture(GL_TEXTURE_2D,walltex); 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_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,WALLTEXSIZE+2,WALLTEXSIZE+2,1,GL_RGBA,GL_UNSIGNED_BYTE,&walltexture[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 dump_room(ROOM *r) { TRIG *t; ROOMLIST *rl; void dump_trig(TRIG *t) { printf(" %p: %s\n",(void *)t,t->text); switch (t->type) { default: printf(" type %d (?)\n",(int)t->type); break; case TT_POINT: printf(" POINT (%g,%g)+%g, theta %g-%g\n", t->point.pt.x, t->point.pt.y, t->point.radius, t->point.mintheta*RADTODEG, t->point.maxtheta*RADTODEG ); break; case TT_BOX: { XYZ p1; XYZ p2; XYZ p3; XYZ p4; p1 = xyzadd(xyzscale(t->box.x,t->box.xmin),xyzscale(t->box.y,t->box.ymin)); p2 = xyzadd(xyzscale(t->box.x,t->box.xmin),xyzscale(t->box.y,t->box.ymax)); p3 = xyzadd(xyzscale(t->box.x,t->box.xmax),xyzscale(t->box.y,t->box.ymax)); p4 = xyzadd(xyzscale(t->box.x,t->box.xmax),xyzscale(t->box.y,t->box.ymin)); printf(" BOX (%g,%g)-(%g,%g)-(%g,%g)-(%g,%g)\n", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y ); } break; } printf(" action %p, arg %p\n",(void *)t->action,(void *)t->arg); } printf("Room \"%s\"\n",r->text); printf(" Floor at %g\n",r->floorz); printf(" Other display rooms:\n"); for (rl=r->otherdisp;rl;rl=rl->link) printf(" %s\n",rl->room->text); printf(" Player triggers:\n"); for (t=r->ptriggers;t;t=t->link) dump_trig(t); printf(" Camera triggers:\n"); for (t=r->ctriggers;t;t=t->link) dump_trig(t); } /* * The way GL transformations work: * * If the current matrix is C and the coordinates presented to (eg) * glVertex3d are V, then the transformed point P is * * P = C V * * or * * [ P(0) ] = [ C( 0) C( 4) C( 8) C(12) ] [ V(0) ] * [ P(1) ] = [ C( 1) C( 5) C( 9) C(13) ] [ V(1) ] * [ P(2) ] = [ C( 2) C( 6) C(10) C(14) ] [ V(2) ] * [ P(3) ] = [ C( 3) C( 7) C(11) C(15) ] [ V(3) ] * * Calling glMultMatrix(M) does C = C M, resulting in * * [ P(0) ] = [ C( 0) C( 4) C( 8) C(12) ] [ M( 0) M( 4) M( 8) M(12) ] [ V(0) ] * [ P(1) ] = [ C( 1) C( 5) C( 9) C(13) ] [ M( 1) M( 5) M( 9) M(13) ] [ V(1) ] * [ P(2) ] = [ C( 2) C( 6) C(10) C(14) ] [ M( 2) M( 6) M(10) M(14) ] [ V(2) ] * [ P(3) ] = [ C( 3) C( 7) C(11) C(15) ] [ M( 3) M( 7) M(11) M(15) ] [ V(3) ] * * Since we are transforming a world-coordinate V into an * eye-coordinate P, the values we need for M are wx/wy/wz. */ static void drawstuff(void) { GLdouble m[16]; ROOMLIST *l; if (dbg & DBG_STATE) { printf("\n"); printf("wx = (%g,%g,%g)\n",wx.x,wx.y,wx.z); printf("wy = (%g,%g,%g)\n",wy.x,wy.y,wy.z); printf("wz = (%g,%g,%g)\n",wz.x,wz.y,wz.z); printf("ploc = (%g,%g,%g)\n",ploc.x,ploc.y,ploc.z); printf("cloc = (%g,%g,%g)\n",cloc.x,cloc.y,cloc.z); printf("ex = (%g,%g,%g)\n",ex.x,ex.y,ex.z); printf("ey = (%g,%g,%g)\n",ey.x,ey.y,ey.z); printf("ez = (%g,%g,%g)\n",ez.x,ez.y,ez.z); printf("frameus = %d\n",frameus); dump_room(curroom); dbg &= ~DBG_STATE; } m[0] = wx.x; m[1] = wx.y; m[2] = wx.z; m[3] = 0; m[4] = wy.x; m[5] = wy.y; m[6] = wy.z; m[7] = 0; m[8] = wz.x; m[9] = wz.y; m[10] = wz.z; m[11] = 0; m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1; glMultMatrixd(&m[0]); glTranslated(-cloc.x,-cloc.y,-cloc.z); glCallList(curroom->list); for (l=curroom->otherdisp;l;l=l->link) glCallList(l->room->list); /* player avatar */ glPushMatrix(); glTranslated(ploc.x,ploc.y,ploc.z); glCallList(playerlist); glPopMatrix(); } static void render(void) { glViewport(0,0,WINX*2,WINY); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glViewport(0,0,2*WINX,WINY); drawstuff(); glXSwapBuffers(disp,win); glXWaitGL(); glXWaitX(); } static void setup_ticks(void) { gettimeofday(&nexttick,0); ticks = 0; } /* * We have to invert the matrix * * [ rx.x ry.x rz.x ] * [ rx.y ry.y rz.y ] * [ rx.z ry.z rz.z ] * * Fortunately, because it's a rotation matrix, it's about as far from * singular as can be; its determinant is theoretically exactly 1. * * The closed-form inverse of * * [ A B C ] * [ D E F ] * [ G H I ] * * is * * [ EI-FH CH-BI BF-CE ] / * [ FG-DI AI-CG CD-AF ] / AEI+BFG+CDH-AFH-BDI-CEG * [ DH-EG BG-AH AE-BD ] / * * We save some multiplications by writing the determinant as * A(EI-FH)+B(FG-DI)+C(DH-EG), for 9 multiplies and 5 adds, versus the * above, which is 12 multiplies and 5 adds - and 6 of those 9 * multiplies can be saved by noticing that three of the terms also * appear in the first column of the (matrix) dividend. * * As mentioned above, in theory, the determinant (the divisor in the * above) is exactly 1. The reason we compute it and divide by it to * help correct for numerical errors. */ static void invert_rotations(XYZ rx, XYZ ry, XYZ rz, XYZ *ix, XYZ *iy, XYZ *iz) { double det; double xx; double xy; double xz; xx = (ry.y * rz.z) - (rz.y * ry.z); xy = (rz.y * rx.z) - (rx.y * rz.z); xz = (rx.y * ry.z) - (ry.y * rx.z); det = (rx.x * xx) + (ry.x * xy) + (rz.x * xz); *ix = (XYZ) { xx / det, xy / det, xz / det }; *iy = (XYZ) { ((rz.x * ry.z) - (ry.x * rz.z)) / det, ((rx.x * rz.z) - (rz.x * rx.z)) / det, ((ry.x * rx.z) - (rx.x * ry.z)) / det }; *iz = (XYZ) { ((ry.x * rz.y) - (rz.x * ry.y)) / det, ((rz.x * rx.y) - (rx.x * rz.y)) / det, ((rx.x * ry.y) - (ry.x * rx.y)) / det }; } static int fire_triggers(TRIG *t, XYZ l, double size) { int fire; for (;t;t=t->link) { if (t->flags & TRIGF_ACTIVE) { switch (t->type) { default: abort(); break; case TT_POINT: { double h; double a; h = hypot(l.x-t->point.pt.x,l.y-t->point.pt.y); if (h <= size+t->point.radius) { a = atan2(l.y-t->point.pt.y,l.x-t->point.pt.x); fire = ((a >= t->point.mintheta) && (a <= t->point.maxtheta)); } else { fire = 0; } } break; case TT_BOX: { double dx; double dy; dx = dot(l,t->box.x); if ((dx < t->box.xmin) || (dx > t->box.xmax)) { fire = 0; } else { dy = dot(l,t->box.y); fire = ((dy >= t->box.ymin) && (dy <= size+t->box.ymax)); } } break; } if (fire) { if (dbg & DBG_FIRING) printf("Trigger fires in %s at (%g,%g)+%g: %s\n",curroom->text,l.x,l.y,size,t->text); (*t->action)(t->arg); return(1); } } } return(0); } static void ptriggers(void) { if (curroom) while (fire_triggers(curroom->ptriggers,ploc,PLAYER_SIZE)) ; } static void ctriggers(void) { if (curroom) while (fire_triggers(curroom->ctriggers,cloc,CAMERA_SIZE)) ; } static void movepby(XYZ dir, double s) { ploc = xyzadd(ploc,xyzscale(dir,s*.1)); ptriggers(); } static void drag_camera(void) { double cz; double ed; XYZ epd; cz = cloc.z; cloc.z = ploc.z; epd = xyzsub(cloc,ploc); ez = xyzunit(epd); ey = (XYZ){0,0,1}; ex = xyzcross(ey,ez); invert_rotations(ex,ey,ez,&wx,&wy,&wz); ed = hypot(ploc.x-cloc.x,ploc.y-cloc.y); if (ed < 1.5) { ed += CAMERA_ADJUST; if (ed > 1.5) ed = 1.5; cloc = xyzadd(ploc,xyzscale(ez,ed)); ctriggers(); } else if (ed > 2) { ed -= CAMERA_ADJUST; if (ed < 2) ed = 2; cloc = xyzadd(ploc,xyzscale(ez,ed)); ctriggers(); } cloc.z = cz; } static void czfloat(void) { double dz; dz = ploc.z + .75; if (cloc.z < dz) { cloc.z += .44e-6 * frameus; if (cloc.z > dz) cloc.z = dz; } else if (cloc.z > dz) { cloc.z -= .44e-6 * frameus; if (cloc.z < dz) cloc.z = dz; } } static void movecby(XYZ dir, double s) { cloc = xyzadd(cloc,xyzscale(dir,s*.1)); ctriggers(); } static void motion(void) { double f; if (kbstate & KBS_SHIFT) f = 12.5; else if (kbstate & KBS_CTRL) f = 2; else f = 5; f *= .3; switch (kbstate & (KBS_ML|KBS_MR)) { case KBS_ML: movepby(ex,-f); movecby(ex,-f); break; case KBS_MR: movepby(ex,f); movecby(ex,f); break; } switch (kbstate & (KBS_MF|KBS_MB)) { case KBS_MF: movepby(ez,-f); break; case KBS_MB: movepby(ez,f); break; } if (kbstate & (KBS_ML|KBS_MR|KBS_MF|KBS_MB)) { drag_camera(); } switch (kbstate & (KBS_RL|KBS_RR)) { case KBS_RL: movecby(ex,f); drag_camera(); break; case KBS_RR: movecby(ex,-f); drag_camera(); break; } czfloat(); } static void keystroke(XKeyEvent *ev, char updn) { KeySym ks; unsigned int bit; int magic; ks = XLookupKeysym(ev,0); bit = 0; magic = ((kbstate & (KBS_LSHF | KBS_RSHF)) == (KBS_LSHF | KBS_RSHF)) && (kbstate & KBS_CTRL); if (magic) printf("magic, ks=%d, updn=%c\n",(int)ks,updn); 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_f: case XK_F: if (magic && (updn == 'd')) dbg ^= DBG_FIRING; break; case XK_g: case XK_G: if (magic && (updn == 'd')) dbg ^= DBG_GATE; 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_q: case XK_Q: if ((updn == 'u') && (kbstate & KBS_SHIFT)) exit(0); break; case XK_p: case XK_P: if (updn == 'd') dbg |= DBG_STATE; 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) { int ms; nexttick.tv_usec += frameus; if (nexttick.tv_usec >= 1000000) { nexttick.tv_usec -= 1000000; nexttick.tv_sec ++; } gettimeofday(&now,0); if ( (now.tv_sec > nexttick.tv_sec) || ( (now.tv_sec == nexttick.tv_sec) && (now.tv_usec >= nexttick.tv_usec) ) ) { frameus += 10000; nexttick = now; return; } ms = ((nexttick.tv_sec - now.tv_sec) * 1000) + 1 + (nexttick.tv_usec / 1000) - (now.tv_usec / 1000); if (ms < 20) { frameus += 10000; } else { frameus -= 10; } if (ms < 1) ms = 1; poll(0,0,ms); gettimeofday(&now,0); } static void tick(void) { motion(); render(); events(); await(); ticks ++; } static void setup_view(void) { ploc = (XYZ){0,0,0}; ex = (XYZ){1,0,0}; ey = (XYZ){0,0,1}; ez = xyzcross(ex,ey); invert_rotations(ex,ey,ez,&wx,&wy,&wz); cloc = (XYZ){0,-5,0}; drag_camera(); } static void setup_input(void) { XGrabKeyboard(disp,win,False,GrabModeAsync,GrabModeAsync,CurrentTime); } static void setup_player_list(void) { int i; double s[PLAYER_SIDES+1]; double c[PLAYER_SIDES+1]; for (i=0;i<=PLAYER_SIDES;i++) { s[i] = sindeg(i*360.0/PLAYER_SIDES) * PLAYER_SIZE; c[i] = cosdeg(i*360.0/PLAYER_SIDES) * PLAYER_SIZE; } playerlist = glGenLists(1); glNewList(playerlist,GL_COMPILE); glColor3f(1,1,1); glBegin(GL_QUAD_STRIP); for (i=0;i<=PLAYER_SIDES;i++) { glVertex3d(c[i],s[i],0); glVertex3d(c[i],s[i],PLAYER_HEIGHT); } glEnd(); glBegin(GL_TRIANGLE_FAN); glVertex3d(0,0,PLAYER_PEAK); for (i=PLAYER_SIDES;i>=0;i--) { glVertex3d(c[i],s[i],PLAYER_HEIGHT); } glEnd(); glEndList(); } static void setup_lists(void) { setup_player_list(); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs = 1; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-seed")) { WANTARG(); seed = strtoul(av[skip],0,0); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); } static XYZ wall_push(XYZ l, TRIG *t, double size) { double dx; if (t->type != TT_BOX) abort(); dx = dot(l,t->box.x); return(xyzadd(xyzadd(xyzscale(t->box.x,dx),xyzscale(t->box.y,t->box.ymax+(size*1.01))),(XYZ){0,0,l.z})); } static XYZ corner_push(XYZ l, TRIG *t, double size) { XYZ d; double n; if (t->type != TT_POINT) abort(); d = xyzsub(l,t->point.pt); n = xyzlength(d); return(xyzadd(xyzadd(t->point.pt,xyzscale(d,(t->point.radius+(size*1.01))/n)),(XYZ){0,0,l.z})); } static void action_wall_push_p(void *tv) { ploc = wall_push(ploc,tv,PLAYER_SIZE); } static void action_wall_push_c(void *tv) { cloc = wall_push(cloc,tv,CAMERA_SIZE); } static void action_corner_push_p(void *tv) { ploc = corner_push(ploc,tv,PLAYER_SIZE); } static void action_corner_push_c(void *tv) { cloc = corner_push(cloc,tv,CAMERA_SIZE); } static void action_gate(void *tov) { curroom = tov; ploc.z = curroom->floorz; if (dbg & DBG_GATE) dump_room(curroom); } static void add_wall(ROOM *r, double x1, double y1, double x2, double y2) { TRIG *t; char *s; asprintf(&s,"WALL (%g,%g)-(%g,%g)",x1,y1,x2,y2); t = malloc(sizeof(TRIG)); t->link = r->ptriggers; r->ptriggers = t; t->text = s; t->type = TT_BOX; t->flags = TRIGF_ACTIVE; t->box.x = xyzunit((XYZ){x2-x1,y2-y1,0}); t->box.y = xyzcross((XYZ){0,0,1},t->box.x); t->box.xmin = dot(t->box.x,(XYZ){x1,y1,0}); t->box.xmax = dot(t->box.x,(XYZ){x2,y2,0}); t->box.ymax = dot(t->box.y,(XYZ){x1,y1,0}); t->box.ymin = t->box.ymax - MAXMOVE; t->action = &action_wall_push_p; t->arg = t; t = malloc(sizeof(TRIG)); t->link = r->ctriggers; r->ctriggers = t; t->text = s; t->type = TT_BOX; t->flags = TRIGF_ACTIVE; t->box.x = xyzunit((XYZ){x2-x1,y2-y1,0}); t->box.y = xyzcross((XYZ){0,0,1},t->box.x); t->box.xmin = dot(t->box.x,(XYZ){x1,y1,0}); t->box.xmax = dot(t->box.x,(XYZ){x2,y2,0}); t->box.ymax = dot(t->box.y,(XYZ){x1,y1,0}); t->box.ymin = t->box.ymax - MAXMOVE; t->action = &action_wall_push_c; t->arg = t; } static void add_corner(ROOM *r, double x, double y, double rad, double degmin, double degmax) { TRIG *t; char *s; asprintf(&s,"CORNER (%g,%g)+%g",x,y,rad); t = malloc(sizeof(TRIG)); t->link = r->ptriggers; r->ptriggers = t; t->text = s; t->type = TT_POINT; t->flags = TRIGF_ACTIVE; t->point.pt.x = x; t->point.pt.y = y; t->point.radius = rad; t->point.mintheta = degmin * DEGTORAD; t->point.maxtheta = degmax * DEGTORAD; t->action = &action_corner_push_p; t->arg = t; t = malloc(sizeof(TRIG)); t->link = r->ctriggers; r->ctriggers = t; t->text = s; t->type = TT_POINT; t->flags = TRIGF_ACTIVE; t->point.pt.x = x; t->point.pt.y = y; t->point.radius = rad; t->point.mintheta = degmin * DEGTORAD; t->point.maxtheta = degmax * DEGTORAD; t->action = &action_corner_push_c; t->arg = t; } static TRIG *add_gate(ROOM *r, double x1, double y1, double x2, double y2) { TRIG *t; char *s; asprintf(&s,"GATE (%g,%g)-(%g,%g)",x1,y1,x2,y2); t = malloc(sizeof(TRIG)); t->link = r->ptriggers; r->ptriggers = t; t->text = s; t->type = TT_BOX; t->flags = TRIGF_ACTIVE; t->box.x = xyzunit((XYZ){x2-x1,y2-y1,0}); t->box.y = xyzcross((XYZ){0,0,1},t->box.x); t->box.xmin = dot(t->box.x,(XYZ){x1,y1,0}); t->box.xmax = dot(t->box.x,(XYZ){x2,y2,0}); t->box.ymax = dot(t->box.y,(XYZ){x1,y1,0}) - PLAYER_SIZE; t->box.ymin = t->box.ymax - MAXMOVE; t->action = &action_gate; t->arg = 0; return(t); } static void wall_rect(XYZ p1, XYZ xdir, XYZ ydir, double x1, double x2, double y1, double y2) { double x0; double y0; XYZ p2; XYZ p3; XYZ p4; p2 = xyzadd(p1,xyzscale(ydir,y2-y1)); p3 = xyzadd(p2,xyzscale(xdir,x2-x1)); p4 = xyzadd(p1,xyzscale(xdir,x2-x1)); x0 = (random() % WALLTEXSUB) / (double)WALLTEXSUB; y0 = (random() % WALLTEXSUB) / (double)WALLTEXSUB; glTexCoord2d(x0+(x1/WALLTEXSUB),y0+(y1/WALLTEXSUB)); glVertex3d(p1.x,p1.y,p1.z); glTexCoord2d(x0+(x1/WALLTEXSUB),y0+(y2/WALLTEXSUB)); glVertex3d(p2.x,p2.y,p2.z); glTexCoord2d(x0+(x2/WALLTEXSUB),y0+(y2/WALLTEXSUB)); glVertex3d(p3.x,p3.y,p3.z); glTexCoord2d(x0+(x2/WALLTEXSUB),y0+(y1/WALLTEXSUB)); glVertex3d(p4.x,p4.y,p4.z); } static void add_otherdisp(ROOM *r, ROOM *o) { ROOMLIST *l; l = malloc(sizeof(ROOMLIST)); l->link = r->otherdisp; r->otherdisp = l; l->room = o; } static void step_room(STAIR *s, int rx, XYZ x, XYZ y, double ox, double oy) { ROOM *r; XYZ c1; XYZ c2; XYZ c3; XYZ c4; XYZ c1x; XYZ c2x; XYZ c3x; XYZ c4x; r = s->rv[rx]; c1 = xyzadd((XYZ){s->x1,s->y1,0},xyzadd(xyzscale(x,ox),xyzscale(y,oy))); c2 = xyzadd(c1,xyzscale(y,s->tread)); c3 = xyzadd(c2,xyzscale(x,s->width)); c4 = xyzadd(c1,xyzscale(x,s->width)); c1x = xyzadd(c1,xyzscale(x,-.1)); c2x = xyzadd(c2,xyzscale(x,-.1)); c3x = xyzadd(c3,xyzscale(x,.1)); c4x = xyzadd(c4,xyzscale(x,.1)); s->gd[rx] = add_gate(r,c1x.x,c1x.y,c4x.x,c4x.y); s->gu[rx] = add_gate(r,c3x.x,c3x.y,c2x.x,c2x.y); r->list = glGenLists(1); glNewList(r->list,GL_COMPILE); glBegin(GL_QUADS); glColor3f(.333,.333,.333); glVertex3d(c1.x,c1.y,r->floorz); glVertex3d(c2.x,c2.y,r->floorz); glVertex3d(c3.x,c3.y,r->floorz); glVertex3d(c4.x,c4.y,r->floorz); if (rx) { glColor3f(.5,.5,.5); glVertex3d(c1.x,c1.y,s->rv[rx-1]->floorz); glVertex3d(c1.x,c1.y,r->floorz); glVertex3d(c4.x,c4.y,r->floorz); glVertex3d(c4.x,c4.y,s->rv[rx-1]->floorz); } // XXX ceiling glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,walltex); glBegin(GL_QUADS); wall_rect((XYZ){c1.x,c1.y,s->floor0},y,(XYZ){0,0,1},oy,oy+s->tread,s->floor0,s->floor1+1); wall_rect((XYZ){c3.x,c3.y,s->floor0},xyzsub((XYZ){0,0,0},y),(XYZ){0,0,1},-(oy+s->tread),-oy,s->floor0,s->floor1+1); glEnd(); glDisable(GL_TEXTURE_2D); glEndList(); } static void landing_room(STAIR *s, int rx, XYZ x, XYZ y, double ox, double oy) { ROOM *r; XYZ c1; XYZ c2; XYZ c3; XYZ c4; XYZ c1x; XYZ c3x; XYZ c4x1; XYZ c4x3; r = s->rv[rx]; c1 = xyzadd((XYZ){s->x1,s->y1,0},xyzadd(xyzscale(x,ox),xyzscale(y,oy))); c2 = xyzadd(c1,xyzscale(y,s->width)); c3 = xyzadd(c2,xyzscale(x,s->width)); c4 = xyzadd(c1,xyzscale(x,s->width)); c1x = xyzadd(c1,xyzscale(x,-.1)); c3x = xyzadd(c3,xyzscale(y,.1)); c4x1 = xyzadd(c4,xyzscale(x,.1)); c4x3 = xyzadd(c4,xyzscale(y,-.1)); s->gd[rx] = add_gate(r,c1x.x,c1x.y,c4x1.x,c4x1.y); s->gu[rx] = add_gate(r,c4x3.x,c4x3.y,c3x.x,c3x.y); r->list = glGenLists(1); glNewList(r->list,GL_COMPILE); glBegin(GL_QUADS); glColor3f(.333,.333,.333); glVertex3d(c1.x,c1.y,r->floorz); glVertex3d(c2.x,c2.y,r->floorz); glVertex3d(c3.x,c3.y,r->floorz); glVertex3d(c4.x,c4.y,r->floorz); if (rx) { glColor3f(.5,.5,.5); glVertex3d(c1.x,c1.y,s->rv[rx-1]->floorz); glVertex3d(c1.x,c1.y,r->floorz); glVertex3d(c4.x,c4.y,r->floorz); glVertex3d(c4.x,c4.y,s->rv[rx-1]->floorz); } // XXX ceiling glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,walltex); glBegin(GL_QUADS); wall_rect((XYZ){c1.x,c1.y,s->floor0},y,(XYZ){0,0,1},oy,oy+s->width,s->floor0,s->floor1+1); wall_rect((XYZ){c2.x,c2.y,s->floor0},x,(XYZ){0,0,1},ox,ox+s->width,s->floor0,s->floor1+1); glEnd(); glDisable(GL_TEXTURE_2D); glEndList(); } static void add_ods(STAIR *s, ROOM *r, int i1, int i2) { int i; for (i=i1;i<=i2;i++) { add_otherdisp(r,s->rv[i]); add_otherdisp(s->rv[i],r); } } static void build_stairwell( ROOM *r1, TRIG *g1, ROOM *r2, TRIG *g2, double x1, double y1, double x2, double y2, double tread, double z1, double z2) { STAIR s; double dz; XYZ xhat[4]; XYZ yhat[4]; int i; XYZ o1; XYZ o2; XYZ o3; XYZ o4; XYZ i1; XYZ i2; XYZ i3; XYZ i4; dz = (z2 - z1) / 19; s.x1 = x1; s.y1 = y1; s.tread = tread; s.width = hypot(x2-x1,y2-y1); s.floor0 = z1; s.floor1 = z2; xhat[0] = xyzunit((XYZ){x2-x1,y2-y1,0}); yhat[0] = xyzcross((XYZ){0,0,1},xhat[0]); for (i=1;i<=3;i++) { xhat[i] = xyzsub((XYZ){0,0,0},yhat[i-1]); yhat[i] = xhat[i-1]; } o1 = xyzadd((XYZ){x1,y1,0},xyzscale(yhat[0],-(s.width+(2*tread)))); o2 = xyzadd((XYZ){x1,y1,0},xyzscale(yhat[0],s.width+(2*tread))); o3 = xyzadd(o2,xyzscale(xhat[0],(2*s.width)+(4*tread))); o4 = xyzadd(o1,xyzscale(xhat[0],(2*s.width)+(4*tread))); i1 = xyzadd((XYZ){x1,y1,0},xyzadd(xyzscale(xhat[0],s.width),xyzscale(yhat[0],-2*tread))); i2 = xyzadd((XYZ){x1,y1,0},xyzadd(xyzscale(xhat[0],s.width),xyzscale(yhat[0],2*tread))); i3 = xyzadd(i2,xyzscale(xhat[0],4*tread)); i4 = xyzadd(i1,xyzscale(xhat[0],4*tread)); for (i=20-1;i>=0;i--) { char *t; s.rv[i] = malloc(sizeof(ROOM)); asprintf(&t,"tread %d",i); s.rv[i]->text = t; s.rv[i]->otherdisp = 0; s.rv[i]->ptriggers = 0; s.rv[i]->ctriggers = 0; s.rv[i]->floorz = z1 + ((z2 - z1) * i) / 19; if ((i < 7) || (i > 12)) { add_wall(s.rv[i],o2.x,o2.y,o1.x,o1.y); add_wall(s.rv[i],i1.x,i1.y,i2.x,i2.y); } if ((i < 12) || (i > 17)) { add_wall(s.rv[i],o3.x,o3.y,o2.x,o2.y); add_wall(s.rv[i],i2.x,i2.y,i3.x,i3.y); } if ((i > 2) && (i < 17)) { add_wall(s.rv[i],o4.x,o4.y,o3.x,o3.y); add_wall(s.rv[i],i3.x,i3.y,i4.x,i4.y); } if ((i < 2) || (i > 7)) { add_wall(s.rv[i],o1.x,o1.y,o4.x,o4.y); add_wall(s.rv[i],i4.x,i4.y,i1.x,i1.y); } } step_room(&s,0,xhat[0],yhat[0],0,0); step_room(&s,1,xhat[0],yhat[0],0,tread); landing_room(&s,2,xhat[0],yhat[0],0,2*tread); step_room(&s,3,xhat[1],yhat[1],-(s.width+(2*tread)),s.width); step_room(&s,4,xhat[1],yhat[1],-(s.width+(2*tread)),s.width+tread); step_room(&s,5,xhat[1],yhat[1],-(s.width+(2*tread)),s.width+(2*tread)); step_room(&s,6,xhat[1],yhat[1],-(s.width+(2*tread)),s.width+(3*tread)); landing_room(&s,7,xhat[1],yhat[1],-(s.width+(2*tread)),s.width+(4*tread)); step_room(&s,8,xhat[2],yhat[2],-((2*s.width)+(4*tread)),-2*tread); step_room(&s,9,xhat[2],yhat[2],-((2*s.width)+(4*tread)),-tread); step_room(&s,10,xhat[2],yhat[2],-((2*s.width)+(4*tread)),0); step_room(&s,11,xhat[2],yhat[2],-((2*s.width)+(4*tread)),tread); landing_room(&s,12,xhat[2],yhat[2],-((2*s.width)+(4*tread)),2*tread); step_room(&s,13,xhat[3],yhat[3],-(s.width+(2*tread)),-(s.width+(4*tread))); step_room(&s,14,xhat[3],yhat[3],-(s.width+(2*tread)),-(s.width+(3*tread))); step_room(&s,15,xhat[3],yhat[3],-(s.width+(2*tread)),-(s.width+(2*tread))); step_room(&s,16,xhat[3],yhat[3],-(s.width+(2*tread)),-(s.width+tread)); landing_room(&s,17,xhat[3],yhat[3],-(s.width+(2*tread)),-s.width); step_room(&s,18,xhat[0],yhat[0],0,-2*tread); step_room(&s,19,xhat[0],yhat[0],0,-tread); for (i=20-1;i>0;i--) { s.gd[i]->arg = s.rv[i-1]; s.gu[i-1]->arg = s.rv[i]; } s.gd[0]->arg = r1; g1->arg = s.rv[0]; s.gu[20-1]->arg = r2; g2->arg = s.rv[20-1]; add_ods(&s,r1,0,19); add_ods(&s,r2,0,19); for (i=19;i>0;i--) add_ods(&s,s.rv[i],0,i-1); } static void setup_maze(void) { ROOM *r1; TRIG *g1; ROOM *r2; TRIG *g2; r1 = malloc(sizeof(ROOM)); r1->text = "lobby"; r1->otherdisp = 0; r1->ptriggers = 0; r1->ctriggers = 0; r1->floorz = 0; add_wall(r1,-2,6,-2,4); add_wall(r1,-2,4,-11,4); add_wall(r1,-10,5,-10,-6); add_wall(r1,-11,-5,11,-5); add_wall(r1,10,-6,10,5); add_wall(r1,11,4,2,4); add_wall(r1,2,4,2,6); add_corner(r1,-2,4,0,-90,0); add_corner(r1,2,4,0,-180,-90); g1 = add_gate(r1,3,5,-3,5); r1->list = glGenLists(1); glNewList(r1->list,GL_COMPILE); glBegin(GL_QUADS); glColor3f(.333,.333,.333); glVertex3d(-11,-6,0); glVertex3d(-11,5,0); glVertex3d(11,5,0); glVertex3d(11,-6,0); glColor3f(1,.667,.667); glVertex3d(-11,-6,1); glVertex3d(11,-6,1); glVertex3d(11,5,1); glVertex3d(-11,5,1); glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,walltex); glBegin(GL_QUADS); wall_rect((XYZ){2,5,0},(XYZ){0,-1,0},(XYZ){0,0,1},0,1,0,1); wall_rect((XYZ){2,4,0},(XYZ){1,0,0},(XYZ){0,0,1},0,8,0,1); wall_rect((XYZ){10,4,0},(XYZ){0,-1,0},(XYZ){0,0,1},0,9,0,1); wall_rect((XYZ){10,-5,0},(XYZ){-1,0,0},(XYZ){0,0,1},0,20,0,1); wall_rect((XYZ){-10,-5,0},(XYZ){0,1,0},(XYZ){0,0,1},0,9,0,1); wall_rect((XYZ){-10,4,0},(XYZ){1,0,0},(XYZ){0,0,1},0,8,0,1); wall_rect((XYZ){-2,4,0},(XYZ){0,1,0},(XYZ){0,0,1},0,1,0,1); glEnd(); glDisable(GL_TEXTURE_2D); glEndList(); r2 = malloc(sizeof(ROOM)); r2->text = "upstairs"; r2->otherdisp = 0; r2->ptriggers = 0; r2->ctriggers = 0; r2->floorz = 2; add_wall(r2,2,4,2,6); add_wall(r2,2,6,11,6); add_wall(r2,10,5,10,16); add_wall(r2,11,15,-11,15); add_wall(r2,-10,16,-10,5); add_wall(r2,-11,6,-2,6); add_wall(r2,-2,6,-2,4); add_corner(r2,2,6,0,90,180); add_corner(r2,-2,6,0,0,90); g2 = add_gate(r2,-2,5,2,5); r2->list = glGenLists(1); glNewList(r2->list,GL_COMPILE); glBegin(GL_QUADS); glColor3f(.333,.333,.333); glVertex3d(-11,5,2); glVertex3d(-11,16,2); glVertex3d(11,16,2); glVertex3d(11,5,2); glColor3f(.667,.667,.667); glVertex3d(-11,5,3); glVertex3d(11,5,3); glVertex3d(11,16,3); glVertex3d(-11,16,3); glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,walltex); glBegin(GL_QUADS); wall_rect((XYZ){-2,5,2},(XYZ){0,1,0},(XYZ){0,0,1},0,1,0,1); wall_rect((XYZ){-2,6,2},(XYZ){-1,0,0},(XYZ){0,0,1},0,8,0,1); wall_rect((XYZ){-10,6,2},(XYZ){0,1,0},(XYZ){0,0,1},0,9,0,1); wall_rect((XYZ){-10,15,2},(XYZ){1,0,0},(XYZ){0,0,1},0,20,0,1); wall_rect((XYZ){10,15,2},(XYZ){0,-1,0},(XYZ){0,0,1},0,9,0,1); wall_rect((XYZ){10,6,2},(XYZ){-1,0,0},(XYZ){0,0,1},0,8,0,1); wall_rect((XYZ){2,6,2},(XYZ){0,-1,0},(XYZ){0,0,1},0,1,0,1); glEnd(); glDisable(GL_TEXTURE_2D); glEndList(); build_stairwell(r1,g1,r2,g2,-2,5,2,5,.5,0,2); lobby = r1; curroom = lobby; } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); open_display(); setup_random(); setup_visual(); setup_X(); setup_context(); setup_textures(); create_window(); setup_gl(); setup_input(); setup_view(); setup_ticks(); setup_lists(); setup_maze(); while (1) tick(); }