/*
 * vtrlt.c		 Kishan P Thomas		07/09/95
 * VisualLab						ADE,DRDO
 *
 * IRIS Performer program using cull and draw  process callbacks.
 * Mouse and keyboard go through GL which is  simpler than mixed
 * model (GLX), but does incur some overhead in the draw process.
 * ( Ref: complex.c)
 * 
 * Text drawing both overlayed on top of the  scene  and  moving 
 * with the scene,  Text string drawn as GL Text.(Ref: text.c) 
 *
 * User Interaction - By Examining the GL - event queue for keyb
 * oard and mouse events.(Ref: bench.c)
 *
 * Visula Database created using:  MultiGen Database modeller in 
 * the Flight database format  MultiGen Revision 14.0 March 1994  
 * Software Systems
 * 
 * Note: Other than the Multigen '.flt' files all  popular  data
 * base file formats  whose loaders are available  can be loaded 
 * using this program
 * 
 * Run-time controls: Mouse & Keyboard
 *
 *	     ESC-key: exits
 *	      F2-key: profile
 *	      F1-key: Assistance Messages
 *	      F3-key: drive/fly modes
 *	  Left-mouse: advance
 *	Middle-mouse: stop
 *	 Right-mouse: retreat
 *   back-space  ' ': stop
 *		 'r': reset position and stop
 *		 'f': freeze
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <getopt.h> /* for cmdline handler */

#include <gl/device.h>
#include <gl/gl.h>

#include <Performer/pf.h>
#include "pfutil.h"
#include "pfflt.h"
#include "pfsgi.h"


static void CullChannel(pfChannel *chan, void *data);
static void DrawChannel(pfChannel *chan, void *data);
static void DrawChannelOne(pfChannel *chan, void *data);
static void OpenPipeline(pfPipe *p);
static void UpdateView(void);
static void ResetView(void);
static void GetGLInput(void);
static void Usage(void);
static void DrawText(pfChannel *chan);
static void DrawIntro(pfChannel *chan);
static void DrawHud(pfChannel *chan);
static void DrawBorder(pfChannel *chan);

#define Button1Mask 0x01
#define Button2Mask 0x02
#define Button3Mask 0x04
#define HISPEED	    0.1f
#define LOSPEED	    0.001f
#define ON	    1
#define OFF	    0

/*
 * structure that resides in shared memory so that the
 * application, cull, and draw processes can access it.
 */
typedef struct
{
    long        exitFlag;
    long        inWindow;
    float       mouseX;
    float	mouseY;
    float	speed;
    long	mouseButtons;
    long	winOriginX;
    long	winOriginY;
    long	winSizeX;
    long	winSizeY;
    long	reset;
    long	message;
    long	travmod;
    long	hudopt;
    long	chan2opt;
    long	freeze;
    pfCoord	view;
    pfVec3	orig_xyz;
    float	sceneSize;
    int		drawStats;
    float	xval;
    float	yval;
    float	zval;
} SharedData;

static SharedData *Shared;

/* light source created and updated in draw-process */

static pfLight *Sun;

/* for configuring multi-process */
static long ProcSplit = PFMP_APPCULLDRAW;

/* write out scene upon read-in - uses pfDebugPrint */
static long WriteScene = 0;
char ProgName[PF_MAXSTRING];

/* for drawing GL text */
static pfMatrix idmat ={{1., 0., 0., 0.},
                        {0., 1., 0., 0.},
                        {0., 0., 1., 0.},
                        {0., 0., 0., 1.}};

pfSphere *bsphere;

/* global variables for the application program*/
int	   drawtextfla_g = ON;
pfNode    *root;
pfSegSet   segset;
int 	   modflag, timer;

 /**************************************************************************/
/*
 * FUNCTION:
 *	LoadFile() 
 *
 * DESCRIPTION:
 *	Load a database file into an IRIS Performer in-memory data 
 *	structure using the filename's extension to intuit the file 
 *	format. The file is sought in the directories named in the
 *	active performer file search path (see pfFilePath for more
 *	details). The geostate argument provides a default state
 *	to those database loaders that accept such an argument. A
 *	value of NULL is a safe default.
 */

extern pfNode *
LoadFile (char *fileName, pfGeoState *geostate)
{
    char	*dot;
    pfNode	*node = NULL;

    /* check argument */
    if (fileName == NULL || *fileName == '\0')
    {
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT,
	    "LoadFile(): NULL filename. loading not possible.");
	return NULL;
    }

    /* look for final "." in filename */
    if ((dot = strrchr(fileName, '.')) == NULL)
    {
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT,
	    "LoadFile(): No file type extension. loading not possible.");
	return NULL;
    }

    /* use filename extension as file-format identifier */
    if (strcmp(dot, ".bin") == 0)
	node = LoadBin(fileName, geostate);
    else 
    if (strcmp(dot, ".dwb") == 0)
	node = LoadDwb(fileName);
    else
    if (strcmp(dot, ".dxf") == 0)
	node = LoadDxf(fileName, geostate);
    else 
    if (strcmp(dot, ".flt") == 0)
#define	USE_SOFTWARE_SYSTEMS_LOADER
#ifdef	USE_SOFTWARE_SYSTEMS_LOADER
	node = LoadFlt(fileName);
#else
	node = LoadFlt11(fileName);
#endif
    else 
    if (strcmp(dot, ".gfo") == 0)
	node = LoadGfo(fileName, geostate);
    else 
    if (strcmp(dot, ".irtp") == 0)
	node = LoadIrtp(fileName, geostate);
/***************************************************************************
    else 
    if (strcmp(dot, ".iv") == 0)
	node = LoadIv(fileName, geostate);
***************************************************************************/
    else 
    if (strcmp(dot, ".lsa") == 0)
	node = LoadLsa(fileName, geostate);
    else 
    if (strcmp(dot, ".lsb") == 0)
	node = LoadLsb(fileName, geostate);
    else 
    if (strcmp(dot, ".obj") == 0)
	node = LoadObj(fileName, geostate);
    else 
    if (strcmp(dot, ".poly") == 0 || strcmp(dot, ".ascii")  == 0)
	node = LoadPoly(fileName, geostate);
    else 
    if (strcmp(dot, ".ptu") == 0)
	node = LoadPtu(fileName, geostate);
    else 
    if (strcmp(dot, ".sgo") == 0)
	node = LoadSgo(fileName, geostate);
    else 
    if (strcmp(dot, ".stla") == 0 || strcmp(dot, ".stl") == 0)
	node = LoadStla(fileName, geostate);
    else 
    if (strcmp(dot, ".stlb") == 0)
	node = LoadStlb(fileName, geostate);
    else 
    if (strcmp(dot, ".sv") == 0)
	node = LoadSv(fileName, geostate);

    else 
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT,
	    "File \"%s\" not loaded -- unknown file type", fileName);

    /* return pointer to in-memory hierarchy */
    return node;
}

/***************************************************************************/


/*
 *	Usage() -- print usage advice and exit. This procedure
 *	is executed in the application process.
 */

static void
Usage (void)
{
    fprintf(stderr, "Usage: %s [-p procSplit] [file.ext ...]\n", ProgName);
    exit(1);
}

/*
*	docmdline() -- use getopt to get command-line arguments, 
*	executed at the start of the application process.
*/

static long
docmdline(int argc, char *argv[])
{
    long	    opt;

    strcpy(ProgName, argv[0]);
    
    /* process command-line arguments */
    while ((opt = getopt(argc, argv, "wp:?")) != -1)
    {
	switch (opt)
	{
	case 'w': 
	    WriteScene = 1;
	    break;
	case 'p':
	    ProcSplit = atoi(optarg);
	    break;
	case '?': 
	    Usage();
	}
    }
    return optind;
}


/*
 *	main() -- program entry point. this procedure
 *	is executed in the application process.
 */

int
main (int argc, char *argv[])
{
    int		    arg;
    int		    found;
    int		    once = 1;
    pfChannel      *chan;
    pfChannel      *chan1;
    pfChannel	   *chan2;
    pfChannel	   *chan3;
    pfScene        *scene;
    pfPipe         *p;
    pfEarthSky     *eSky;
    pfBox           bbox;
    float	    far = 30000.0f;
    pfCoord	    view;	
    pfVec3	    backxyz;
    pfVec3	    backhpr;
    
    if (argc < 2)
	Usage();
    
    arg = docmdline(argc, argv);
    
    pfInit();

    /* configure multi-process selection */
    pfMultiprocess(ProcSplit);
    
    /* for GL text drawing */
    bsphere = (pfSphere*) pfMalloc (sizeof(pfSphere), pfGetSharedArena());
    
    /* allocate shared before fork()'ing parallel processes */
    Shared = (SharedData*)pfMalloc(sizeof(SharedData), pfGetSharedArena());
    Shared->inWindow = 0;
    Shared->exitFlag = 0;
    Shared->drawStats = 0;
    Shared->speed = 0.0f;
    Shared->travmod = 1;
    Shared->freeze = 0;
    
    /* initiate multi-processing mode set in pfMultiprocess call */
    pfConfig();
    
    scene = pfNewScene();
    
    /* specify directories where geometry and textures exist */
    if (!(getenv("PFPATH")))
        pfFilePath(
                   "."
                   ":./data"
                   ":../data"
                   ":../../data"
                   ":/usr/src/Performer/data"
                   );
    fprintf(stderr,"FilePath: %s\n", pfGetFilePath());
    
    /* load files named by command line arguments */
    for (found = 0; arg < argc; arg++)
    {
	if ((root = LoadFile(argv[arg], NULL)) != NULL)
	{
	    pfAddChild(scene, root);
	    found++;
	}
    }
    
    /* if no files successfully loaded, terminate program */
    if (!found)
	Usage();
    
    /* determine extent of scene's geometry */
    pfuTravCalcBBox(scene, &bbox);
    
    p = pfGetPipe(0);
    pfPhase(PFPHASE_FLOAT);
    
    /* Open and configure full screen GL window. */
    pfInitPipe(p, OpenPipeline);
    
    pfFrameRate(20.0f);
    
    /* Create the main channel */
    chan  = pfNewChan(p);
    pfChanCullFunc(chan, CullChannel);
    pfChanDrawFunc(chan, DrawChannel);
    pfChanScene(chan, scene);
    pfChanNearFar(chan, 0.1f, far);
    /* vertical FOV is matched to window aspect ratio */
    pfChanFOV(chan, 45.0f, -1.0f);
    
    /* Create an earth/sky model that draws sky/ground/horizon */
    eSky = pfNewESky();
    pfESkyMode(eSky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
    pfESkyAttr(eSky, PFES_GRND_HT, -10.0f);
    pfChanESky(chan, eSky);

    /* set initial view position 
    Shared->yval=0.0f;
    Shared->zval=0.5f;*/
	
    {
	float sceneSize;
	/* Set initial view to be "in front" of scene */
	
	/* view point at center of bbox */
	pfAddVec3(Shared->view.xyz, bbox.min, bbox.max);
	pfScaleVec3(Shared->view.xyz, 0.5f, Shared->view.xyz);
	
	/* find max dimension */
	sceneSize = bbox.max[PF_X] - bbox.min[PF_X];
	sceneSize = PF_MAX2(sceneSize, bbox.max[PF_Y] - bbox.min[PF_Y]);
	sceneSize = PF_MAX2(sceneSize, bbox.max[PF_Z] - bbox.min[PF_Z]);
	sceneSize = PF_MIN2(sceneSize, 0.5f * far);
	Shared->sceneSize = sceneSize;
	
	/* offset so all is visible */
	Shared->view.xyz[PF_Y] -=      sceneSize;
	Shared->view.xyz[PF_Z] += 0.25f*sceneSize;

	pfSetVec3(Shared->view.xyz,0.0f,0.0f,10.0f);
	
	pfSetVec3(Shared->view.hpr, 0.0f, 0.0f, 0.0f);

	pfChanView(chan, Shared->view.xyz, Shared->view.hpr);
	
    }
    
    /* set up intersection segment, pointing down 
    for "terrain" following */
    segset.activeMask = 1;
    segset.isectMask = 0xFFFF;
    segset.mode = PFTRAV_IS_PRIM|PFTRAV_IS_NORM|PFTRAV_IS_CULL_BACK;
    pfSetVec3(segset.segs[0].dir, 0.0f, 0.0f, -1.0f);
    segset.segs[0].length = 50000.0f;
    
     /* save initial viewpoint for reset */
    pfCopyVec3(Shared->orig_xyz, Shared->view.xyz);
    
    /* Clock initialised for timing the intro */
    pfInitClock (0.0f);
    
    /* main simulation loop */
    while (!Shared->exitFlag)
    {
	
	/* TERRAIN FOLLOWING */
	pfHit	  **hits[100];
	long	  isect;
	
	
	/* wait until next frame boundary */
	pfSync();
	
	/* Set view parameters. */    
	pfChanView(chan, Shared->view.xyz, Shared->view.hpr);
	
	if(Shared->chan2opt)
	    {
		if(once)
		    {
			once = !once;
			
			/* Create 1st channel */
			/* PERSPECTIVE BIRD'S EYE VIEW */
			chan1 = pfNewChan(p);
			pfChanCullFunc(chan1, CullChannel);
			pfChanDrawFunc(chan1, DrawChannelOne);
			pfChanScene(chan1, scene);
			pfChanNearFar(chan1, 0.1f, far);
			/* vertical FOV is matched to window aspect ratio */
			pfChanFOV(chan1, 60.0f, -1.0f);
			pfESkyMode(eSky, PFES_BUFFER_CLEAR, PFES_SKY);
			pfESkyAttr(eSky, PFES_GRND_HT, 5.0f);
			pfChanESky(chan1, eSky);
			pfChanViewport(chan1, 0.03, 0.3, 0.72, 0.97);
			
			/* 'chan1' slaved to masterchan 'chan' */
			/*pfAttachChan(chan,chan1);*/
			/*ViewState->masterChan = chan;*/			
			/* Set chan1's viewing offset to top view */
			/* viewpoint does not change */
			pfSetVec3(backxyz, 0.0f, -200.0f,0.0f);
			/* viewdirection changes */
			pfSetVec3(backhpr,0.0f, 0.0f, 0.0f); 
			pfChanViewOffsets(chan1,backxyz,backhpr);

			/* Create 2nd channel */
			/* VERTICAL AREAL VIEW */
			chan2 = pfNewChan(p);
			pfChanCullFunc(chan2, CullChannel);
			pfChanDrawFunc(chan2, DrawChannelOne);
			pfChanScene(chan2, scene);
			pfChanNearFar(chan2, 0.1f, far);
			/* vertical FOV is matched to window aspect ratio */
			pfChanFOV(chan2, 45.0f, -1.0f);
			pfESkyMode(eSky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
			pfESkyAttr(eSky, PFES_GRND_HT, -10.0f);
			pfChanESky(chan2, eSky);
			pfChanViewport(chan2, 0.69, .97, 0.72, 0.97);

			/* 'chan2' slaved to masterchan 'chan' */
			/*pfAttachChan(chan,chan2);*/
			/*ViewState->masterChan = chan;*/			
			/* Set chan2's viewing offset to getback view */
			/* viewpoint does not change */
			pfSetVec3(backxyz, 0.0f, 20.0f,500.0f);
			/* viewdirection changes */
			pfSetVec3(backhpr,0.0f, -90.0f, 0.0f); 
			pfChanViewOffsets(chan2,backxyz,backhpr);
			
			/* Create 3rd channel */
			/* REAR VIEW MIRROR */
			chan3 = pfNewChan(p);
			pfChanCullFunc(chan3, CullChannel);
			pfChanDrawFunc(chan3, DrawChannelOne);
			pfChanScene(chan3, scene);
			pfChanNearFar(chan3, 0.1f, far);
			/* vertical FOV is matched to window aspect ratio */
			pfChanFOV(chan3, 45.0f, -1.0f);
			pfESkyMode(eSky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
			pfESkyAttr(eSky, PFES_GRND_HT, -10.0f);
			pfChanESky(chan3, eSky);
			pfChanViewport(chan3, 0.36, 0.63, 0.72, 0.97);

			/* 'chan3' slaved to masterchan 'chan' */
			/*pfAttachChan(chan,chan3);*/
			/*ViewState->masterChan = chan;*/			
			/* Set chan3's viewing offset to getback view */
			/* viewpoint does not change */
			pfSetVec3(backxyz, 0.0f, 0.0f,0.0f);
			/* viewdirection changes */
			pfSetVec3(backhpr,-180.0f, 0.0f, 0.0f); 
			pfChanViewOffsets(chan3,backxyz,backhpr);
			
			
			
		    }
		/* Set view parameters. */
		pfChanView(chan2, Shared->view.xyz, Shared->view.hpr);
		pfChanView(chan3, Shared->view.xyz, Shared->view.hpr);
		pfChanView(chan1, Shared->view.xyz, Shared->view.hpr);
	    }
	
	/* Set view parameters. */    
	pfChanView(chan, Shared->view.xyz, Shared->view.hpr);
  
	/* initiate traversal using current state */
	pfFrame();
	/* compute new view */
	if (Shared->reset)
	    ResetView();
	else
	if(!Shared->freeze)
	    UpdateView();
}
    
    /* terminate cull and draw processes (if they exist) */
    pfExit();
    
    /* exit to operating system 
    exit(0);*/
}
/*
 *	Resetview() Resetes the viewpoint
 */
static void
ResetView(void)
{
    pfCoord *view = &Shared->view;
    Shared->reset = 0;
    Shared->hudopt = 0.0;
    pfCopyVec3(view->xyz, Shared->orig_xyz);
    pfSetVec3(view->hpr, 0.0f, 0.0f, 0.0f);
}


/* 
 *	UpdateView() updates the eyepoint based on the information
 *	placed in Shared memory by GetGLInput().
 */
static void    
UpdateView(void)
{
    static float speed = 0.0f;
    static float speedLimit = 4.0f;
    pfCoord *view = &Shared->view;
    float cp;
    
    pfHit   **hits[100];
    long    isect;
    
    if (!Shared->inWindow)
    {
	speed = 0;
	return;
    }
    switch (Shared->mouseButtons)
    {
    	case Button1Mask: /* LEFTMOUSE: faster forward or slower backward*/
		if (speed > 0.0f)
	    	speed *= 1.2f;
	    else
		speed /= 1.2f;
	
	    if (PF_ABSLT(speed, LOSPEED * Shared->sceneSize))
	    speed = LOSPEED * Shared->sceneSize;
	    else if (speed >=  HISPEED * Shared->sceneSize)
	    speed = HISPEED * Shared->sceneSize;
	    break;
	case Button2Mask:	/* MIDDLEMOUSE: stop moving and pick */
	    speed = 0.0f;
	    break;
	case Button3Mask: /* RIGHTMOUSE: faster backward or slower foreward*/
	    if (speed < 0.0f)
	    speed *= 1.2f;
	    else
		speed /= 1.2f;
	
	    if (PF_ABSLT(speed, LOSPEED * Shared->sceneSize))
		speed = -LOSPEED * Shared->sceneSize;
	    else if (speed <=  -HISPEED * Shared->sceneSize)
		speed = -HISPEED * Shared->sceneSize;
	    break;
    }

    /* update view direction */
    view->hpr[PF_H] -= Shared->mouseX * PF_ABS(Shared->mouseX);
    view->hpr[PF_P]  = Shared->mouseY * PF_ABS(Shared->mouseY) * 90.0f;
    cp = cosf(PF_DEG2RAD(view->hpr[PF_P])); 
    if(!Shared->travmod) view->hpr[PF_P] = 0.0f; 
    view->hpr[PF_R]  = 0.0f;
    
    /*update view position*/ 
    /*cp = cosf(PF_DEG2RAD(view->hpr[PF_P]));*/
    view->xyz[PF_X] += speed*sinf(-PF_DEG2RAD(view->hpr[PF_H])*cp);
    view->xyz[PF_Y] += speed*cosf(-PF_DEG2RAD(view->hpr[PF_H])*cp);
    view->xyz[PF_Z] += speed*sinf( PF_DEG2RAD(view->hpr[PF_P]));
    
    if(!Shared->travmod)
    {
	/* update location of intersection segment */
	pfSetVec3(segset.segs[0].pos, view->xyz[PF_X], view->xyz[PF_Y], view->xyz[PF_Z]);
	
	/* do an intersection test against the scene graph */
	isect = pfSegsIsectNode(root, &segset, hits);
	
	/* if successful, set our height to that of
	the point of contact, plus a small offset  */
	if(isect)
	{
	    pfVec3 pnt;
	    pfQueryHit(*hits[0], PFQHIT_POINT, &pnt);
	     
	    view->xyz[PF_Z] = pnt[PF_Z] + 0.5;
	}
    }
    
}

/*
 *	OpenPipeline() -- create a pipeline: setup the window system,
 *	the IRIS GL, and IRIS Performer. this procedure is executed in
 *	the draw process (when there is a separate draw process).
 */

static void
OpenPipeline(pfPipe *p)
{
    float xSize = 800;
    float ySize = 500;

    /* negotiate with window-manager */
    /* Open,Size and Position graphics window */
    foreground();
    prefposition(200, 900, 300, 800);
    winopen("R.E.A.L.I.T.Y   by  K + I + S + H + A + N");
    winconstraints();
    /* register events of note with event-queue manager */
    qdevice(ESCKEY);
    qdevice(F1KEY);
    qdevice(F2KEY);
    qdevice(F3KEY);
    qdevice(SPACEKEY);
    qdevice(KEYBD);
    
    /* negotiate with GL */
    /* Configure window with reasonable defaults. */
    pfInitGfx(p);
    
    /* create a light source in the "south-west" (QIII) */
    Sun = pfNewLight(NULL);
    pfLightPos(Sun, -0.3f, -0.3f, 1.0f, 0.0f);
    
    /* create a default texture environment */
    pfApplyTEnv(pfNewTEnv(NULL));
    
    /* create a default lighting model */
    pfApplyLModel(pfNewLModel(NULL));
    
    pfApplyMtl(pfNewMtl(NULL));
    
    /* enable culling of back-facing polygons */
    pfCullFace(PFCF_BACK);
    
    /*
     * These enables should be set to reflect the majority of the
     * database. If most geometry is not textured, then texture
     * should be disabled. However, you then need to change the
     * FLIGHT-format file reader. (pfflt.c)
     */
    pfEnable(PFEN_TEXTURE);
    pfEnable(PFEN_LIGHTING);
    pfEnable(PFEN_FOG);
}

/*
 *	CullChannel() -- traverse the scene graph and generate a
 * 	display list for the draw process.  This procedure is 
 *	executed in the cull process.
 */

static void
CullChannel(pfChannel *channel, void *data)
{
    /* 
     * pfDrawGeoSet or other display listable Performer routines
     * could be invoked before or after pfCull()
     */
    
    pfCull();
}

/*
 *	DrawChannel() -- draw a channel and read input queue. this
 *	procedure is executed in the draw process (when there is a
 *	separate draw process).
 */
static void
DrawChannel (pfChannel *channel, void *data)
{
    float t;
    
    /* rebind light so it stays fixed in position */
    pfLightOn(Sun);
    
    /* erase framebuffer and draw Earth-Sky model */
    pfClearChan(channel);
    
    /* invoke Performer draw-processing for this frame */
    pfDraw();
    
    /* Draw Introductory Information */
    /* Check the time */
    t = pfGetTime();
    if( (t < 20.0f)&&(!Shared->drawStats) )
	DrawIntro(channel);
	
    if(Shared->hudopt)
	DrawHud(channel);
    
    /* draw Performer throughput statistics */
    if (Shared->drawStats)
	pfDrawChanStats(channel);
    
    
    /* draw text on the scene */
    if (Shared->message)
        DrawText(channel);
    
    
    if(modflag)
	{
	     DrawModinfo(channel, 1);
	     modflag++;
	     if(modflag >= 99)
		modflag = 0;		  
	}

    /* read window origin and size (it may have changed) */
    pfGetPipeSize(pfGetChanPipe(channel),
		    &Shared->winSizeX, &Shared->winSizeY);
    pfGetPipeOrigin(pfGetChanPipe(channel), 
		      &Shared->winOriginX, &Shared->winOriginY);
   
    GetGLInput();
}

/*
 *	DrawChannelOne() -- draw a channel and read input queue. this
 *	procedure is executed in the draw process (when there is a
 *	separate draw process).For the inset  Views
 */
static void
DrawChannelOne (pfChannel *channel, void *data)
{
    float       t;
    
    
    /* rebind light so it stays fixed in position */
    pfLightOn(Sun);
    
    /* erase framebuffer and draw Earth-Sky model */
    pfClearChan(channel);
    
    /* invoke Performer draw-processing for this frame */
    pfDraw();
    
    /* Draw Borders */
    if (Shared->chan2opt)
	DrawBorder(channel);
    
    /* draw Performer throughput statistics */
    if (Shared->drawStats)
	pfDrawChanStats(channel);
    
    /* read window origin and size (it may have changed) */
    pfGetPipeSize(pfGetChanPipe(channel),
		    &Shared->winSizeX, &Shared->winSizeY);
    pfGetPipeOrigin(pfGetChanPipe(channel), 
		      &Shared->winOriginX, &Shared->winOriginY);
    
    
}

static void
GetGLInput(void)
{
    long x, y;
    
    
    while (qtest())
    {
	short           value;
	long            device = qread(&value);
	
	/* only act on key-down transitions */
	if (value)
	{
	    switch (device)
	    {
		/* ESC-key signals end of simulation */
	    case ESCKEY:
		Shared->exitFlag = 1;
		break;
		
		/* F2-key toggles channel-stats display */
	    case F2KEY:
		Shared->drawStats = !Shared->drawStats;
		Shared->message = 0;
		Shared->hudopt  = 0;
		break;

	    	/* F1-key toggles message display */
	    case F1KEY:
		Shared->message = !Shared->message;
		break;
		
		/* F3-key toggles drive/fly modes */
	    case F3KEY:
		Shared->travmod = !Shared->travmod;
		modflag = 1;
		break;
		
	    case SPACEKEY:
		    Shared->speed = 0.0f;
		    break;
	    case KEYBD:
		    switch(value)
		    {
		    case 'r':
		        Shared->speed = 0.0f;
			Shared->reset = 1;
			break;
		    case 'h':
		        Shared->hudopt = !Shared->hudopt;
			Shared->drawStats = 0;
			Shared->message = 0;
			break;
		    case 'b':
		        Shared->chan2opt = !Shared->chan2opt;
			Shared->hudopt = 0;
			Shared->drawStats = 0;
			Shared->message = 0;
			break;
		    case 'm':
			Shared->message = !Shared->message;
			Shared->hudopt = 0;
			Shared->drawStats = 0;
			break;
		    case 'f':
			Shared->freeze = !Shared->freeze;
			break;
		    }
	    }
	}
    }
    
    
    
    /* read cursor position (may be outside our window) */
    x = getvaluator(MOUSEX);
    y = getvaluator(MOUSEY);
    
    Shared->inWindow = 0;
    /* update cursor virtual position when cursor inside window */
    if (x >= Shared->winOriginX && 
	x < (Shared->winOriginX + Shared->winSizeX) && 
	y >= Shared->winOriginY && 
	y < (Shared->winOriginY + Shared->winSizeY))
    {
	Shared->inWindow = 1;
	
	Shared->mouseX = 2.0f * ((x - Shared->winOriginX) /
				 (float)Shared->winSizeX) - 1.0f;
	Shared->mouseY = 2.0f * ((y - Shared->winOriginY) /
				 (float)Shared->winSizeY) - 1.0f;

	Shared->mouseButtons = ((getbutton(LEFTMOUSE)   ? Button1Mask : 0) |
				(getbutton(MIDDLEMOUSE) ? Button2Mask : 0) |
				(getbutton(RIGHTMOUSE)  ? Button3Mask : 0));
    }
    

}

/*
 *  Drawtext() -- Draws the text string on the 
 *  scene as GL text		     
 */
static void
DrawText(pfChannel *channel)
{
	static pfMatrix tempmat;

        pfClearChan (channel);
        pfDraw();

	zfunction (ZF_ALWAYS);  /* text always drawn */
	zwritemask (0x0);

	/* text moving with the scene */
        pfPushState();

                pfBasicState();

                cmov (0.5f * bsphere->radius, 0.0f, 0.5f * bsphere->radius);
                cpack(0xFFFFFF);
                charstr ("VIRTUAL REALITY");

        pfPopState();

	/* text overlaid on top of scene, like a HUD */
        pfPushState();
                pfBasicState();

                mmode (MPROJECTION);
                getmatrix (tempmat);
                mmode (MVIEWING);

                ortho2 (-10.0, 10.0, -10.0, 10.0);
		RGBmode();
		gconfig();
		RGBcolor(200, 0, 255);

                pfPushMatrix();
                        pfLoadMatrix(idmat);
                        cmov2 (-9.5, 9.5);
                        charstr ("EVEN BETTER THAN THE REAL THING    +   Kishan P.Thomas   +    08/09/95");
			pfLoadMatrix(idmat);
                        cmov2 (-9.5, -8.0);
			RGBcolor(255, 255, 255);
                        charstr (">> Left-mouse:Advance, Middle-mouse:Stop, Right-mouse:Retreat *");
			pfLoadMatrix(idmat);
                        cmov2 (-9.5, -8.5);
			charstr (">> r -Reset * f -Freeze * h -HUD * b -Other views * Esc -Exit * ");
			pfLoadMatrix(idmat);
                        cmov2 (-9.5, -9.0);
			charstr (">> F1-Assistance * F2 -Statistics * F3 -Mod *");
                pfPopMatrix();

                mmode (MPROJECTION);
                loadmatrix (tempmat);
                mmode (MVIEWING);

        pfPopState();

	zfunction (ZF_LEQUAL);  /* the default */
	zwritemask (0xffffffff);
}

/*
 *  DrawIntro() -- Draws the introduction 
 *  messages as GL text	for 10 seconds	     
 */
static void
DrawIntro(pfChannel *channel)
{
	static pfMatrix tempmat;
	long vert1[2] = {-5, -5};
	long vert2[2] = {-5,  5};
	long vert3[2] = { 5,  5};
	long vert4[2] = { 5, -5};
	
	
	
        pfClearChan (channel);
        pfDraw();
	
	
	zfunction (ZF_ALWAYS);  /* text always drawn */
	zwritemask (0x0);
	
	
	/* text overlaid on top of scene, like a HUD */
	pfPushState();
                pfBasicState();

                mmode (MPROJECTION);
                getmatrix (tempmat);
                mmode (MVIEWING);