/* Copyright (c) Mark J. Kilgard, 1996. */ /* This program is freely distributable without licensing fees and is provided without guarantee or warrantee expressed or implied. This program is -not- in the public domain. */ /* * 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 #include #include #include #include #include #include #include #include #include #ifdef noGLwidget #include /* Motif drawing area widget */ #else #ifdef noMotifGLwidget #include /* Pure Xt OpenGL drawing area widget. */ #else #include /* Motif OpenGL drawing area widget. */ #endif #endif #include #include #include #include #define PI 3.14159265358979323846 #define PI_2 1.57079632679489661923 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[] = { "*sgiMode: true", /* Enable SGI Indigo Magic look & feel */ "*useSchemes: all", /* and SGI schemes. */ "*title: OpenGL paper plane demo", "*glxarea*width: 300", "*glxarea*height: 300", NULL }; Display *dpy; Bool doubleBuffer = True, moving = False, made_current = 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]; 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); glVertex3f(-20, 20, -19); glVertex3f(20, 20, -19); glColor3f(0.0, 0.0, 1.0); glVertex3f(20, -20, -19); glVertex3f(-20, -20, -19); glEnd(); /* Render 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. */ glVertex3f(-7.0, 0.0, 2.0); glVertex3f(-1.0, 0.0, 3.0); glColor3f(red = planes[i].red, green = planes[i].green, blue = planes[i].blue); glVertex3f(-1.0, 7.0, 3.0); /* Left side. */ glColor3f(0.6 * red, 0.6 * green, 0.6 * blue); glVertex3f(0.0, 0.0, 0.0); glVertex3f(0.0, 8.0, 0.0); /* Right side. */ glVertex3f(1.0, 0.0, 3.0); glVertex3f(1.0, 7.0, 3.0); /* Final tip of right wing. */ glColor3f(red, green, blue); glVertex3f(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) + PI_2) * sin(theta) - PI_2) * 180 / 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 (rand() % 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 = (rand() % 20) * 0.001 + 0.02; if (rand() & 0x1) planes[i].speed *= -1; planes[i].theta = ((float) (rand() % 257)) * 0.1111; tick_per_plane(i); if (!moving) draw(glxarea); return; } XBell(dpy, 100); /* Cannot 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) { if (made_current) { #ifdef noGLwidget Dimension width, height; /* Unfortunately, drawing area resize callback does not give height and width via its parameters. */ glXWaitX(); XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); glViewport(0, 0, (GLint) width, (GLint) height); #else GLwDrawingAreaCallbackStruct *resize = (GLwDrawingAreaCallbackStruct *) callData; glXWaitX(); glViewport(0, 0, (GLint) resize->width, (GLint) resize->height); #endif } } 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 Xt 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; 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; } } int 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 = False; } /* Create an OpenGL rendering context. */ cx = glXCreateContext(dpy, vi, /* No display list sharing. */ None, /* Favor direct rendering. */ 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); /* Must 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, frame, GLwNvisualInfo, vi, NULL); #else glxarea = XtVaCreateManagedWidget("glxarea", glwMDrawingAreaWidgetClass, frame, GLwNvisualInfo, vi, NULL); #endif #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 = 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. */ srand(getpid()); add_plane(); add_plane(); add_plane(); XtAppMainLoop(app); return 0; /* ANSI C requires main to return int. */ }