/* * paperplane can be compiled to use a "single visual" for the entire window * hierarchy and render OpenGL into a standard Motif drawing area widget: * * cc -o sv_paperplane paperplane.c -DnoGLwidget -lGL -lXm -lXt -lX11 -lm * * Or paperplane can be compiled to use the default visual for most of * the window hierarchy but render OpenGL into a special "OpenGL widget": * * cc -o glw_paperplane paperplane.c -lGLw -lGL -lXm -lXt -lX11 -lm */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <math.h> #include <Xm/MainW.h> #include <Xm/RowColumn.h> #include <Xm/PushB.h> #include <Xm/ToggleB.h> #include <Xm/CascadeB.h> #include <Xm/Frame.h> #ifdef noGLwidget #include <Xm/DrawingA.h> /* Motif drawing area widget */ #else /** NOTE: in IRIX 5.2, the OpenGL widget headers are mistakenly in **/ /** <GL/GLwDrawA.h> and <GL/GlwMDraw.h> respectively. Below are the **/ /** _official_ standard locations. **/ #ifdef noMotifGLwidget #include <X11/GLw/GLwDrawA.h> /* pure Xt OpenGL drawing area widget */ #else #include <X11/GLw/GLwMDrawA.h> /* Motif OpenGL drawing area widget */ #endif #endif #include <X11/keysym.h> #include <GL/gl.h> #include <GL/glu.h> #include <GL/glx.h> static int dblBuf[] = { GLX_DOUBLEBUFFER, GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, None }; static int *snglBuf = &dblBuf[1]; static String fallbackResources[] = { #ifdef IRIX_5_2_or_higher "*sgiMode: true", /* try to enable IRIX 5.2+ look & feel */ "*useSchemes: all", /* and SGI schemes */ #endif "*title: OpenGL paper plane demo", "*glxarea*width: 300", "*glxarea*height: 300", NULL }; Display *dpy; GLboolean doubleBuffer = GL_TRUE, moving = GL_FALSE, made_current = GL_FALSE; XtAppContext app; XtWorkProcId workId = 0; Widget toplevel, mainw, menubar, menupane, btn, cascade, frame, glxarea; GLXContext cx; XVisualInfo *vi; #ifdef noGLwidget Colormap cmap; #endif Arg menuPaneArgs[1], args[1]; #define MAX_PLANES 15 struct { float speed; /* zero speed means not flying */ GLfloat red, green, blue; float theta; float x, y, z, angle; } planes[MAX_PLANES]; #define v3f glVertex3f /* v3f was the short IRIS GL name for glVertex3f */ void draw(Widget w) { GLfloat red, green, blue; int i; glClear(GL_DEPTH_BUFFER_BIT); /* paint black to blue smooth shaded polygon for background */ glDisable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glBegin(GL_POLYGON); glColor3f(0.0, 0.0, 0.0); v3f(-20, 20, -19); v3f(20, 20, -19); glColor3f(0.0, 0.0, 1.0); v3f(20, -20, -19); v3f(-20, -20, -19); glEnd(); /* paint planes */ glEnable(GL_DEPTH_TEST); glShadeModel(GL_FLAT); for (i = 0; i < MAX_PLANES; i++) if (planes[i].speed != 0.0) { glPushMatrix(); glTranslatef(planes[i].x, planes[i].y, planes[i].z); glRotatef(290.0, 1.0, 0.0, 0.0); glRotatef(planes[i].angle, 0.0, 0.0, 1.0); glScalef(1.0 / 3.0, 1.0 / 4.0, 1.0 / 4.0); glTranslatef(0.0, -4.0, -1.5); glBegin(GL_TRIANGLE_STRIP); /* left wing */ v3f(-7.0, 0.0, 2.0); v3f(-1.0, 0.0, 3.0); glColor3f(red = planes[i].red, green = planes[i].green, blue = planes[i].blue); v3f(-1.0, 7.0, 3.0); /* left side */ glColor3f(0.6 * red, 0.6 * green, 0.6 * blue); v3f(0.0, 0.0, 0.0); v3f(0.0, 8.0, 0.0); /* right side */ v3f(1.0, 0.0, 3.0); v3f(1.0, 7.0, 3.0); /* final tip of right wing */ glColor3f(red, green, blue); v3f(7.0, 0.0, 2.0); glEnd(); glPopMatrix(); } if (doubleBuffer) glXSwapBuffers(dpy, XtWindow(w)); if(!glXIsDirect(dpy, cx)) glFinish(); /* avoid indirect rendering latency from queuing */ #ifdef DEBUG { /* for help debugging, report any OpenGL errors that occur per frame */ GLenum error; while((error = glGetError()) != GL_NO_ERROR) fprintf(stderr, "GL error: %s\n", gluErrorString(error)); } #endif } void tick_per_plane(int i) { float theta = planes[i].theta += planes[i].speed; planes[i].z = -9 + 4 * cos(theta); planes[i].x = 4 * sin(2 * theta); planes[i].y = sin(theta / 3.4) * 3; planes[i].angle = ((atan(2.0) + M_PI_2) * sin(theta) - M_PI_2) * 180 / M_PI; if (planes[i].speed < 0.0) planes[i].angle += 180; } void add_plane(void) { int i; for (i = 0; i < MAX_PLANES; i++) if (planes[i].speed == 0) { #define SET_COLOR(r,g,b) \ planes[i].red=r; planes[i].green=g; planes[i].blue=b; break; switch (random() % 6) { case 0: SET_COLOR(1.0, 0.0, 0.0); /* red */ case 1: SET_COLOR(1.0, 1.0, 1.0); /* white */ case 2: SET_COLOR(0.0, 1.0, 0.0); /* green */ case 3: SET_COLOR(1.0, 0.0, 1.0); /* magenta */ case 4: SET_COLOR(1.0, 1.0, 0.0); /* yellow */ case 5: SET_COLOR(0.0, 1.0, 1.0); /* cyan */ } planes[i].speed = (random() % 20) * 0.001 + 0.02; if (random() & 0x1) planes[i].speed *= -1; planes[i].theta = ((float) (random() % 257)) * 0.1111; tick_per_plane(i); if (!moving) draw(glxarea); return; } XBell(dpy, 100); /* can't add any more planes */ } void remove_plane(void) { int i; for (i = MAX_PLANES - 1; i >= 0; i--) if (planes[i].speed != 0) { planes[i].speed = 0; if (!moving) draw(glxarea); return; } XBell(dpy, 100); /* no more planes to remove */ } void resize(Widget w, XtPointer data, XtPointer callData) { Dimension width, height; if(made_current) { XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); glViewport(0, 0, (GLint) width, (GLint) height); } } void tick(void) { int i; for (i = 0; i < MAX_PLANES; i++) if (planes[i].speed != 0.0) tick_per_plane(i); } Boolean animate(XtPointer data) { tick(); draw(glxarea); return False; /* leave work proc active */ } void toggle(void) { moving = !moving; /* toggle */ if (moving) workId = XtAppAddWorkProc(app, animate, NULL); else XtRemoveWorkProc(workId); } void quit(Widget w, XtPointer data, XtPointer callData) { exit(0); } void input(Widget w, XtPointer data, XtPointer callData) { XmDrawingAreaCallbackStruct *cd = (XmDrawingAreaCallbackStruct *) callData; char buf[1]; KeySym keysym; int rc; if(cd->event->type == KeyPress) if(XLookupString((XKeyEvent *) cd->event, buf, 1, &keysym, NULL) == 1) switch (keysym) { case XK_space: if (!moving) { /* advance one frame if not in motion */ tick(); draw(w); } break; case XK_Escape: exit(0); } } void map_state_changed(Widget w, XtPointer data, XEvent * event, Boolean * cont) { switch (event->type) { case MapNotify: if (moving && workId != 0) workId = XtAppAddWorkProc(app, animate, NULL); break; case UnmapNotify: if (moving) XtRemoveWorkProc(workId); break; } } main(int argc, char *argv[]) { toplevel = XtAppInitialize(&app, "Paperplane", NULL, 0, &argc, argv, fallbackResources, NULL, 0); dpy = XtDisplay(toplevel); /* find an OpenGL-capable RGB visual with depth buffer */ vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf); if (vi == NULL) { vi = glXChooseVisual(dpy, DefaultScreen(dpy), snglBuf); if (vi == NULL) XtAppError(app, "no RGB visual with depth buffer"); doubleBuffer = GL_FALSE; } /* create an OpenGL rendering context */ cx = glXCreateContext(dpy, vi, /* no display list sharing */ None, /* favor direct */ GL_TRUE); if (cx == NULL) XtAppError(app, "could not create rendering context"); /* create an X colormap since probably not using default visual */ #ifdef noGLwidget cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone); /* * Establish the visual, depth, and colormap of the toplevel * widget _before_ the widget is realized. */ XtVaSetValues(toplevel, XtNvisual, vi->visual, XtNdepth, vi->depth, XtNcolormap, cmap, NULL); #endif XtAddEventHandler(toplevel, StructureNotifyMask, False, map_state_changed, NULL); mainw = XmCreateMainWindow(toplevel, "mainw", NULL, 0); XtManageChild(mainw); /* create menu bar */ menubar = XmCreateMenuBar(mainw, "menubar", NULL, 0); XtManageChild(menubar); #ifdef noGLwidget /* Hack around Xt's unfortunate default visual inheritance. */ XtSetArg(menuPaneArgs[0], XmNvisual, vi->visual); menupane = XmCreatePulldownMenu(menubar, "menupane", menuPaneArgs, 1); #else menupane = XmCreatePulldownMenu(menubar, "menupane", NULL, 0); #endif btn = XmCreatePushButton(menupane, "Quit", NULL, 0); XtAddCallback(btn, XmNactivateCallback, quit, NULL); XtManageChild(btn); XtSetArg(args[0], XmNsubMenuId, menupane); cascade = XmCreateCascadeButton(menubar, "File", args, 1); XtManageChild(cascade); #ifdef noGLwidget menupane = XmCreatePulldownMenu(menubar, "menupane", menuPaneArgs, 1); #else menupane = XmCreatePulldownMenu(menubar, "menupane", NULL, 0); #endif btn = XmCreateToggleButton(menupane, "Motion", NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc)toggle, NULL); XtManageChild(btn); btn = XmCreatePushButton(menupane, "Add plane", NULL, 0); XtAddCallback(btn, XmNactivateCallback, (XtCallbackProc)add_plane, NULL); XtManageChild(btn); btn = XmCreatePushButton(menupane, "Remove plane", NULL, 0); XtAddCallback(btn, XmNactivateCallback, (XtCallbackProc)remove_plane, NULL); XtManageChild(btn); XtSetArg(args[0], XmNsubMenuId, menupane); cascade = XmCreateCascadeButton(menubar, "Planes", args, 1); XtManageChild(cascade); /* create framed drawing area for OpenGL rendering */ frame = XmCreateFrame(mainw, "frame", NULL, 0); XtManageChild(frame); #ifdef noGLwidget glxarea = XtVaCreateManagedWidget("glxarea", xmDrawingAreaWidgetClass, frame, NULL); #else #ifdef noMotifGLwidget /* notice glwDrawingAreaWidgetClass lacks an 'M' */ glxarea = XtVaCreateManagedWidget("glxarea", glwDrawingAreaWidgetClass, #else glxarea = XtVaCreateManagedWidget("glxarea", glwMDrawingAreaWidgetClass, #endif frame, GLwNvisualInfo, vi, NULL); #endif XtAddCallback(glxarea, XmNexposeCallback, (XtCallbackProc)draw, NULL); XtAddCallback(glxarea, XmNresizeCallback, resize, NULL); XtAddCallback(glxarea, XmNinputCallback, input, NULL); /* set up application's window layout */ XmMainWindowSetAreas(mainw, menubar, NULL, NULL, NULL, frame); XtRealizeWidget(toplevel); /* * Once widget is realized (ie, associated with a created X window), we * can bind the OpenGL rendering context to the window. */ glXMakeCurrent(dpy, XtWindow(glxarea), cx); made_current = GL_TRUE; /* setup OpenGL state */ glClearDepth(1.0); glClearColor(0.0, 0.0, 0.0, 0.0); glMatrixMode(GL_PROJECTION); glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 20); glMatrixMode(GL_MODELVIEW); /* add three initial random planes */ srandom(getpid()); add_plane(); add_plane(); add_plane(); /* start event processing */ XtAppMainLoop(app); }