Appendix E. Example Program Using Xt and a WorkProc

This appendix contains an example program that uses Xt, IRIS IM, and the IRIS IM version of the Silicon Graphics widget. The program displays a planet with a moon, orbiting a sun, and uses a WorkProc for the animation.

/* opensolar.c
 * opensolar displays a planet with a moon, orbiting a sun.
 * To compile:
 * cc -O -o opensolar opensolar.c -lXm -lGLw -lm -lGLU -lGL
 */

#include <Xm/Xm.h>
#include <Xm/Frame.h>
#include <Xm/Form.h>
#include <X11/keysym.h>
#include <X11/StringDefs.h>
#include <GL/GLwMDrawA.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "malloc.h"

typedef struct _spin {
  short year;
} SPINDATA, *SPINPTR;

/* function prototypes */
void main(int argc, char **argv);
void initCB (Widget w, XtPointer client_data,
             XtPointer call_data);
void exposeCB (Widget w, XtPointer spin,
               XtPointer call_data);
void resizeCB (Widget w, XtPointer spin,
               XtPointer call_data);
void inputCB (Widget w, XtPointer client_data,
              XtPointer call_data);
Boolean drawWP (XtPointer spin);
void drawscene(SPINPTR spin);
void setbeachball(int stripes);
void beachball(unsigned long color1, unsigned long color2);

XtAppContext app_context;
XtWorkProcId workprocid = NULL;

GLXContext glx_context;
Display * global_display;
Window global_window;


/* main
 * This program shows a solar system, with a sun, planet, and
 * moon (in OpenGL). The user can exit with the ESCape key
 * or through the window manager menu.
 */
void main(int argc, char **argv)
{
  Arg wargs[15];
  int n;
  Widget glw, toplevel, frame, form;
  SPINPTR spin;
  static String fallback_resources[] = {
    "*frame*shadowType: SHADOW_IN", "*glwidget*width: 750",
    "*glwidget*height: 600", "*glwidget*rgba: TRUE",
    "*glwidget*doublebuffer: TRUE",
    "*glwidget*allocateBackground: TRUE", NULL
  };

  /* create main data structure, spin pointer */
  spin = (SPINPTR) malloc (sizeof (SPINDATA));
  spin->year = 0;
  toplevel = XtAppInitialize(
     &app_context, /* Application context */
     "Opensolar", /* Application class */
     NULL, 0, /* command line option list */
     &argc, argv, /* command line args */
     fallback_resources,
     NULL, /* argument list */
     0); /* number of arguments */

  n = 0;
  form = XmCreateForm(toplevel, "form", wargs, n);
  XtManageChild(form);

  n = 0;
  XtSetArg(wargs[n], XtNx, 30); 
  n++;
  XtSetArg(wargs[n], XtNy, 30); 
  n++;
  XtSetArg(wargs[n], XmNbottomAttachment, XmATTACH_FORM); 
  n++;
  XtSetArg(wargs[n], XmNleftAttachment, XmATTACH_FORM); 
  n++;
  XtSetArg(wargs[n], XmNrightAttachment, XmATTACH_FORM); 
  n++;
  XtSetArg(wargs[n], XmNtopAttachment, XmATTACH_FORM); 
  n++;
  XtSetArg(wargs[n], XmNleftOffset, 30); 
  n++;
  XtSetArg(wargs[n], XmNbottomOffset, 30); 
  n++;
  XtSetArg(wargs[n], XmNrightOffset, 30); 
  n++;
  XtSetArg(wargs[n], XmNtopOffset, 30); 
  n++;
  frame = XmCreateFrame (form, "frame", wargs, n);
  XtManageChild (frame);

  n = 0;
  glw = GLwCreateMDrawingArea(frame, "glwidget", wargs, n);
  XtManageChild (glw);
  XtAddCallback(glw, GLwNginitCallback, initCB,
                (XtPointer) NULL);
  XtAddCallback(glw, GLwNexposeCallback, exposeCB,
                (XtPointer) spin);
  XtAddCallback(glw, GLwNresizeCallback, resizeCB,
                (XtPointer) spin);
  XtAddCallback(glw, GLwNinputCallback, inputCB,
                (XtPointer) NULL);

  XtRealizeWidget(toplevel); /* instantiate it now */
  XtAppMainLoop(app_context); /* loop for events */
} /* end main() */


/* initCB 
 * The initCB subroutine initializes graphics modes and
 * transformation matrices.
 */
void initCB (Widget w, XtPointer client_data,
             XtPointer call_data)
{
  Arg args[1];
  XVisualInfo *vi;

  XtSetArg(args[0], GLwNvisualInfo, &vi);
  XtGetValues(w, args, 1);

  global_display = XtDisplay(w);
  global_window = XtWindow(w);
  glx_context = glXCreateContext(global_display, vi, 0,
                                 GL_FALSE);
} /* end initCB() */


/* exposeCB() and resizeCB() are called when the window
 * is uncovered, moved, or resized.
 */
void exposeCB (Widget w, XtPointer ptr, XtPointer call_data)
{
  SPINPTR spin;
  static char firstTime = 0x1;
  GLwDrawingAreaCallbackStruct *call_ptr;

  call_ptr = (GLwDrawingAreaCallbackStruct *) call_data;
  GLwDrawingAreaMakeCurrent(w, glx_context);
  if (firstTime) {
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective(45.0, (GLfloat)(call_ptr->width)
                   /(GLfloat)(call_ptr->height), 1.0, 25.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
    glTranslatef(0.0, 0.0, -12.0);
    workprocid = XtAppAddWorkProc(app_context, drawWP, ptr);
      /* ptr is spin */
    firstTime = 0;
  }
  spin = (SPINPTR) ptr;
  drawscene(spin);
}

void resizeCB (Widget w, XtPointer ptr, XtPointer call_data)
{
  GLwDrawingAreaCallbackStruct *call_ptr;
  SPINPTR spin;

  spin = (SPINPTR) ptr;
  call_ptr = (GLwDrawingAreaCallbackStruct *) call_data;
  GLwDrawingAreaMakeCurrent(w, glx_context);
  glViewport (0, 0, (GLsizei) (call_ptr->width-1),
              (GLsizei) (call_ptr->height-1));
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective(45.0, (GLfloat)(call_ptr->width) /
                 (GLfloat)(call_ptr->height), 1.0, 25.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity ();
  glTranslatef(0.0, 0.0, -12.0);
  drawscene(spin);
}


/* inputCB() handles all types of input from the GL widget.
 * The KeyRelease handles the ESCape key, so that it exits
 * the program. 
 */
void inputCB (Widget w, XtPointer client_data,
              XtPointer call_data)
{
  char buffer[1];
  KeySym keysym;
  GLwDrawingAreaCallbackStruct *call_ptr;
  XKeyEvent *kevent;

  call_ptr = (GLwDrawingAreaCallbackStruct *) call_data;
  kevent = (XKeyEvent *) (call_ptr->event);
  switch(call_ptr->event->type) {
  case KeyRelease:
    /* Must convert the keycode to a keysym before
     * checking if it is an escape
     */
    if (XLookupString(kevent,buffer,1,&keysym,NULL) == 1 
                      && keysym == (KeySym)XK_Escape)
      exit(0);
    break;
  default:
    break;
  }
}


/* drawWP() is called by the WorkProc. When the scene
 * is in automatic motion, the WorkProc calls this routine,
 * which adds 1 degree (10 tenths) to the cumulative amount
 * of rotation. drawscene() is called, so the image is
 * redrawn. It returns(FALSE) so the WorkProc does not
 * discontinue operation.
 */
Boolean drawWP (XtPointer ptr)
{
  SPINPTR spin;

  spin = (SPINPTR) ptr;
  spin->year = (spin->year + 10) % 3600;
  drawscene (spin);
  return (FALSE);
}

/* drawscene
 * drawscene calculates angles relative to the spin->year
 * and then draws sun, planet, and moon.
 */
void drawscene(SPINPTR spin)
{
  short sunangle;
  /* actual dist is 1.5e8 km; mult by 3.0e-8 fudgefactor */
  float earthdist = 4.5;
  short dayangle;
  float earthscale = 0.5;
  short monthangle;
  float moondist = 0.9;
  float moonscale = 0.2;

  glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);

  glPushMatrix();
  glRotatef(10.0, 1.0, 0.0, 0.0); /* tilt entire scene */
  glPushMatrix();
  sunangle = (spin->year*365/25) % 3600;
  /* sun rotates on axis every 25 days */
  glRotatef(.1*(sunangle), 0.0, 1.0, 0.0);
  /* cpack format color1, color2 */
  /* swapped by hand: was beachball(0x20C0FF, 0x200FFFF); */
  beachball(0xFFC02000, 0xFFFF0020);
  glPopMatrix();
  glPushMatrix();
  glRotatef(.1*(spin->year), 0.0, 1.0, 0.0);
  glTranslatef(earthdist, 0.0, 0.0);
  glPushMatrix();
  dayangle = (spin->year*50) % 3600;
  /* dayangle fudged so earth rotation can be seen */
  glRotatef(.1*(dayangle), 0.0, 1.0, 0.0);
  glScalef(earthscale, earthscale, earthscale);
  glColor3f(0.0, 0.0, 1.0);
  /* swap by hand; was beachball(0xFF0000, 0xC02000);*/
  beachball(0x0000FF00, 0x0020C000); /* earth */
  glPopMatrix();
  monthangle = (spin->year*365/28) % 3600;
  glRotatef(.1*(monthangle), 0.0, 1.0, 0.0);
  glTranslatef(moondist, 0.0, 0.0);
  glScalef(moonscale, moonscale, moonscale);
  glColor3f(1.0, 1.0, 1.0);
  /* swap by hand; was beachball(0xFFFFFF, 0xC0C0C0); */
  beachball(0xFFFFFF00, 0xC0C0C000); /* moon */
  glPopMatrix();
  glPopMatrix();
  glXSwapBuffers(global_display, global_window);
} /* end drawscene() */


/*
 * BEACHBALL
 */

/* three dimensional vector */
typedef float vector[3];
vector front =  { 0.0, 0.0, 1.0 };
vector back =   { 0.0, 0.0, -1.0 };
vector top =    { 0.0, 1.0, 0.0 };
vector bottom = { 0.0, -1.0, 0.0 };
vector right =  { 1.0, 0.0, 0.0 };
vector left =   { -1.0, 0.0, 0.0 };
vector center = { 0.0, 0.0, 0.0 };

/* Number of colored stripes. Should be even to look right */
#define BEACHBALL_STRIPES 12
/* Default number of polygons making up a stripe. Should */
/* be even */
#define BEACHBALL_POLYS 16

/* array of vertices making up a stripe */
vector stripe_point[BEACHBALL_POLYS + 3];

/* has the beachball been initialized */
Boolean beachball_initialized = FALSE;

/* Number of polygons making up a stripe */
int beachball_stripes;

/* Number of vertices making up a stripe */
int stripe_vertices;

/* Initializes beachball_point array to a stripe of unit */
/* radius. */
void setbeachball(int stripes)
{
  int i,j;
  float x,y,z; /* vertex points */
  float theta,delta_theta; /* angle from top pole to bottom*/
  float offset; /* offset from center of stripe to vertex */
  /* radius of cross-section at current latitude */
  float cross_radius;
  float cross_theta; /* angle occupied by a stripe */

  beachball_stripes = stripes;

  /* polys distributed by even angles from top to bottom */
  delta_theta = M_PI/((float)BEACHBALL_POLYS/2.0);
  theta = delta_theta;
  cross_theta = 2.0*M_PI/(float)beachball_stripes;

  j = 0;
  stripe_point[j][0] = top[0];
  stripe_point[j][1] = top[1];
  stripe_point[j][2] = top[2];
  j++;

  for (i = 0; i < BEACHBALL_POLYS; i += 2) {
    cross_radius = fsin(theta);
    offset = cross_radius * ftan(cross_theta/2.0);

    stripe_point[j][0] = - offset;
    stripe_point[j][1] = fcos(theta);
    stripe_point[j][2] = cross_radius;
    j++;

    stripe_point[j][0] = offset;
    stripe_point[j][1] = stripe_point[j-1][1];
    stripe_point[j][2] = stripe_point[j-1][2];
    j++;

    theta += delta_theta;
  } /* end for */

  stripe_point[j][0] = bottom[0];
  stripe_point[j][1] = bottom[1];
  stripe_point[j][2] = bottom[2];

  stripe_vertices = j + 1;

  beachball_initialized = TRUE;
}


/* Draws a canonical beachball. The colors are cpack values
 * when in RGBmode.
 */
void beachball(unsigned long c1, unsigned long c2)
{
  float angle, delta_angle;
  int i, j;

  if (! beachball_initialized)
    setbeachball(BEACHBALL_STRIPES);

  angle = 0.0;
  delta_angle = 360.0/(float)beachball_stripes;

  for (i = 0; i < beachball_stripes; i++) {
    if ( i%2 == 0)
      glColor4ubv((GLubyte *)(&c1));
    else
      glColor4ubv((GLubyte *)(&c2));
    glPushMatrix();
    glRotatef(angle, 0.0, 1.0, 0.0);
    angle += delta_angle;

    glBegin(GL_TRIANGLE_STRIP);
    for (j = 0; j < stripe_vertices; j++)
      glVertex3fv(stripe_point[j]);
    glEnd();
    glPopMatrix();
  }
}