Skip to content
Snippets Groups Projects
szgPartiview.cc 43.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Syzygy-related functions for szgPartiview, including main program.
     */
    
    static char local_id[] = "$Id$";
    
    
     * Revision 1.18  2010/04/14 21:49:41  slevy
     * Get the head transformation right so that "where" is the inverse of "jump".
     *
    
    slevy's avatar
    slevy committed
     * Revision 1.17  2009/03/28 22:48:47  slevy
     * Add some debug code (matstr()).
     * Re-orthogonalize the matrix in FlyNav::navUpdate().
     * Otherwise it'll blow up within a few dozen iterations.
     *
    
     * Revision 1.16  2009/03/27 16:30:29  slevy
     * Add first attempt at flyer navigation.
     * New "-fly" config option, and "fly <buttonpov>,<buttoncenter>,<tspeed>,<rspeed>"
     * control command.  I don't know which button is which, so it's configurable on the fly.
     *
    
     * Revision 1.15  2009/03/27 03:26:51  slevy
     * When "jump"ing, align the target point with the head's position,
     * rather than the CAVE origin.  Use position only -- keep the orientation
     * of the CAVE.
     *
     * This still might not be right.  Might be better to put the
     * target point just behind the head, so an origin crosshair doesn't bug us.
     *
    
     * Revision 1.14  2009/03/26 20:47:13  slevy
     * Add -plog option to control logging.  If present, each szg process logs all msg()/warn() messages
     * to "parti.NNNN.log" for ProcessID NNNN.
     *
    
     * Revision 1.13  2009/03/25 07:47:39  slevy
     * ar_log_XXX logs don't seem to go anywhere, so also append to
     * parti.NNNN.log, where NNNN is the SZGClient ProcessID.
     * Maybe this too should be changed -- should we just try to log
     * to one big file, to limit clutter?  Every line is already labelled with [PID] now.
     *
     * Don't use exit(0) in command parser.  Set a shared quit flag instead,
     * and have everybody (?) exit later.
     *
     * <string>.append( C_string, len ) never copies in the string's trailing \000,
     * so add it ourselves.
     *
     * Only create a listenSocket if we're the master -- avoid races to grab the port.
     * I'm not sure when is best to determine master-hood, so out of superstition
     * we do it in the first preExchange callback.
     *
     * Yes, we *do* need to initialize cmdbuf_ to be *empty*.
     *
    
     * Revision 1.12  2009/03/14 21:32:02  slevy
     * Initialize the cmdbuf transfer-buffer size to 1 byte.
     * szg gets mad if we try to select size 0.
     *
    
     * Revision 1.11  2008/07/28 09:22:58  slevy
     * Add CMDTEST hook for testing PpCmd packing/unpacking.  Looks like it might work.
     *
    
     * Revision 1.10  2008/07/28 08:49:48  slevy
     * Scrap pp_ui_postinit().  It just overwrites settings that (a) were initialized
     * properly earlier and (b) might have been changed by command-line I/O.
     * I.e. let the "run" command work if included in a .cf file.
     *
    
     * Revision 1.9  2008/07/28 08:39:41  slevy
     * onPreExchange: Point sockp at a new bufferedSocket before passing to ar_accept()!
     * Tidy list insert/deletion.
     * Call clock_tick() periodically in master to allow time to pass.
     * onPostExchange: Add more error checking.
     * main: Hack to allow running under Linux+freeglut in standalone mode.
     *
    
     * Revision 1.8  2008/07/25 16:10:16  slevy
     * Lots of changes from Will Davis: new socket stuff
     * for listening for commands from network;
     * transfer fields; attempts to get "play" to play, etc.
     * Some from Stuart: PpCmd uses string object;
     * passing PpCmd commands by transfer-field
     * from master to slaves.
     *
    
     * Revision 1.7  2008/07/22 23:20:07  slevy
     * Need to resize() when adding new elements, not just reserve().
     *
    
     * Revision 1.6  2008/07/22 18:46:14  slevy
     * Use ar_log_<whatever>() for msg() logging.  Send to either _remark or _warning
     * according to whether msg() or warn() was called.
     *
    
     * Revision 1.5  2008/07/22 17:49:58  slevy
     * From Will Davis: globalize argc/argv for use in callback later.
     * onWindowStartGL() returns void.
     *
     * Implement PpCmd interface for serializing commands for network transport.
     *
    
     * Revision 1.4  2008/07/15 08:40:17  slevy
     * Don't mention "virtual" on *implementation* methods.
     *
    
     * Revision 1.3  2008/07/14 17:17:48  slevy
     * Attempt to glue in some arMasterSlaveFramework methods -- maybe enough to draw,
     * though not to process any commands after command-line startup yet.
     *
     */
    
    
    #include "arPrecompiled.h"
    #include "arMasterSlaveFramework.h"
    
    #include <stdio.h>
    #include <stdlib.h>
    #include "config.h"
    
    
    # include "winjunk.h"
    #endif
    
     
    #include "specks.h"
    #include "szgPartiview.h"
    #include "shmem.h"
    #include <string.h>
    #include <stdarg.h>
    #include <ctype.h>
    #include <math.h>
    #include <errno.h>
    
    #include "partiviewc.h"
    #include "findfile.h"
    
    
    struct Ppszg  ppszg;
    
    static int specks_commandstr( struct stuff **stp, const char *str ) {
      int result;
      if(stp == NULL || *stp == NULL || str == NULL)
        return 0;
    
    #define MAXARGS 128
      char *av[MAXARGS];
      int ac;
      char *txt = (char *)malloc( strlen(str) + 1 );
      char *s = txt;
      strcpy(txt, str);
      for(ac = 0; ac < MAXARGS-1; ac++) {
        av[ac] = strtok(s, " \t\n");
        if(av[ac] == NULL) break;
        s = NULL;
      }
      av[ac] = NULL;
      result = specks_parse_args( stp, ac,av);
      free(txt);
      return result;
    }
    
    int specks_commandfmt( struct stuff **stp, const char *fmt, ... ) {
      char cmd[1024], *cp;
      int ok;
      va_list args;
      va_start(args, fmt);
      vsprintf(cmd, fmt, args);
      va_end(args);
      for(cp = cmd; isspace(*cp); cp++)
          ;
      if(*cp == '\0') return 1;	// accept null command implicitly
      ok = specks_commandstr( stp, cmd );
      if(ok) parti_redraw();
      else msg("Unrecognized control command: %s", cmd);
      return ok;
    }
    
    
    /*
     * These two are a quick fix to using argc and argv[] in the
     * onStart() function. I don't believe you can pass parameters
     * into, but I could be wrong.
     *
     */
    int globalArgc;
    char** globalArgv;
    
    // partiview <-> syzygy navigation.
    
    void copyfrom_arMatrix( Matrix *dstT, const arMatrix4 *srcarT )
    {
        int i;
        for(i = 0; i < 16; i++)
    	dstT->m[i] = srcarT->v[i];
    }
    
    void copyto_arMatrix( arMatrix4 *dstarT, const Matrix *srcT )
    {
        int i;
        for(i = 0; i < 16; i++)
    	dstarT->v[i] = srcT->m[i];
    }
    
    /* =================================================================== */
    
    
    class FlyNav {
      public:
        bool   flying;	// Flying enabled?  If not, ignore all the below.
        int    bpov;	// While this button# pressed, fly, with point-of-view as rotation center
        int    bcen;	// While this button# pressed, fly, with "center" as rot. center
        float  tspeed;	// translation speed scale factor
        float  rspeed;	// rotation speed scale factor
        int    bstarted;	// has flying started?  If so, on which button?  Else -1.
        Matrix TstartW2C;	// wand-to-cave tfm at time of whichever button press
        Matrix TstartC2W;	// cave-to-wand, ditto
    
        FlyNav();
        void setPovButton( int b ) { bpov = b; }
        void setCenterButton( int b ) { bcen = b; }
        void navUpdate();
        void config( const char * s );
    };
    
    FlyNav::FlyNav() {
        flying = false;
        bpov = 0;
        bcen = 1;
        tspeed = 2;
        rspeed = 2;
    
        bstarted = -1;
        TstartW2C = Tidentity;	// initial Wand-to-Cave matrix
        TstartC2W = Tidentity;	// initial Cave-to-Wand matrix (inverse of Wand-to-Cave)
    }
    
    void FlyNav::config( const char *str ) {
        flying = true;
        if(0 == strcmp(str, "off")) {
    	flying = false;
    	return;
        } else if(0 == strcmp(str, "on")) {
    	flying = true;
    	return;
        }
        sscanf( str, "%d%*c%d%*c%f%*c%f%*c", &bpov, &bcen, &tspeed, &rspeed );
    }
    
    
    slevy's avatar
    slevy committed
    char *matstr( Matrix *T )
    {
        static char s[128];
        sprintf(s, "%g %g %g %g  %g %g %g %g  %g %g %g %g  %g %g %g %g",
    	    T->m[0],T->m[1],T->m[2],T->m[3],
    	    T->m[4],T->m[5],T->m[6],T->m[7],
    	    T->m[8],T->m[9],T->m[10],T->m[11],
    	    T->m[12],T->m[13],T->m[14],T->m[15]);
        return s;
    }
    
    
    void FlyNav::navUpdate() {
        if(!flying)
    	return;
    
        arMatrix4 wand2C = ppszg.getMatrix( 1 );	// wand-to-CAVE tfm
    
        if(this->bstarted < 0) {
    	if(this->bpov >= 0 && ppszg.getOnButton( bpov )) {
    	    this->bstarted = this->bpov;
    	} else if(this->bcen >= 0 && ppszg.getOnButton( this->bcen )) {
    	    this->bstarted = this->bcen;
    	}
    	if(this->bstarted >= 0) {
    	    copyfrom_arMatrix( &this->TstartW2C, &wand2C );
    	    eucinv( &this->TstartC2W, &this->TstartW2C );
    	}
    
        } else {
    	if(ppszg.getButton( this->bstarted )) {
    	    // How much has wand moved since button was pressed?
    
    	    arMatrix4 arC2world = ar_getNavMatrix();
    
    	    Matrix TC2world;
    	    copyfrom_arMatrix( &TC2world, &arC2world );
    	    // TC2world is CAVE-to-world transform
    
    
    slevy's avatar
    slevy committed
    	    float deltat = 0.001 * ppszg.getLastFrameTime();
    
    	    
    
    	    Matrix TnowW2C;	// wand-to-CAVE
    	    copyfrom_arMatrix( &TnowW2C, &wand2C );
    	    Matrix TdeltaC;
    
    slevy's avatar
    slevy committed
    	    mmmul( &TdeltaC,  &this->TstartC2W,&TnowW2C );
    
    
    	    Point pstartC, pnowC, dpC;
    
    slevy's avatar
    slevy committed
    	    vgettranslation( &pstartC, &this->TstartW2C );
    
    	    vgettranslation( &pnowC, &TnowW2C );
    	    vsub( &dpC, &pnowC, &pstartC );
    	    float mag = vlength( &dpC );
    
    	    vscale( &dpC,  sqrt(mag) * this->tspeed * deltat, &dpC );
    
    	    // now dpC is the incremental translation in CAVE-oriented coords
    
    	    Quat dqC;
    	    
    	    tfm2quat( &dqC, &TdeltaC );
    
    	    Quat qidentity = { 1, 0, 0, 0 };
    	    if(dqC.q[0] < 0)
    		qidentity.q[0] = -1;	/* interpolate in same quat hemisphere */
    
    	    float qfrac = this->rspeed * deltat;
    	    qcomb( &dqC,  qfrac,&dqC, 1-qfrac,&qidentity );
    	    qnorm( &dqC, &dqC );
    	    Matrix TincrC;
    	    quat2tfm( &TincrC, &dqC );
    
    	    // now TincrC is the incremental rotation in CAVE-oriented coordinates
    	    // stuff the translation in too
    	    vsettranslation( &TincrC, &dpC );
    
    
    	    // where is the center-of-rotation? in either CAVE or world coordinates
    	    const Point *pcenC, *pcenW;
    	    if(this->bstarted == this->bpov) {
    		pcenC = &pnowC;		// rotate about the wand
    		pcenW = NULL;
    	    } else {
    		pcenC = NULL;
    		pcenW = ppszg.scene->center();
    	    }
    
    	    Matrix TnewC2world;
    	    mconjugate( &TnewC2world, &TC2world, &TincrC,
                    &TC2world, NULL/*world2C auto-computed*/,
                    pcenW, pcenC );
    
    
    slevy's avatar
    slevy committed
    
    	    /* re-orthogonalize */
    	    float scaling = vlength( (Point *)&TC2world.m[0] );
    	    Point p;
    	    Quat q;
    	    vgettranslation( &p, &TnewC2world );
    	    tfm2quat( &q, &TnewC2world );
    	    quat2tfm( &TnewC2world, &q );
    	    for(int i = 0; i < 12; i++)
    		TnewC2world.m[i] *= scaling;
    	    vsettranslation( &TnewC2world, &p );
    
    
    
    	    arMatrix4 arnewC2world;
    	    copyto_arMatrix( &arnewC2world, &TnewC2world );
    	    ar_setNavMatrix( arnewC2world );
    	} else {
    	    bstarted = -1;
    	}
        }
    }
    
    // Global instance
    FlyNav flyer;
    
    
    ////////////////////////////////////////////////////////////////////////
    
    
    int pp_read( struct stuff **, int argc, char *argv[], char *fromfname, void * ) {
    
        /* this module defines only one command at present ... */
        if(!strcmp( argv[0], "subcam")) {
    	if(argc >= 9) {
    	    parti_make_subcam( argv[1], argc-2, argv+2 );
    	} else {
    
    	    msg("%s: subcam: expected name az el rol L R B T, not %s",
    		fromfname, rejoinargs( 1, argc, argv ));
    
    	}
    	return 1;
        }
        return 0;
    }
    
    
    void parti_lighting() {
        static GLuint lightlist[MAXDSPCTX];
    
        int ctx = get_dsp_context();	/* this would come from Syzygy wall number, say */
        int listmaking = 0;
    
        if((unsigned int)ctx < MAXDSPCTX) {
    	if(lightlist[ctx] > 0) {
    	    glCallList( lightlist[ctx] );
    	    return;
    	} else {
    	    lightlist[ctx] = glGenLists( 1 );
    	    glNewList( lightlist[ctx], GL_COMPILE_AND_EXECUTE );
    	    listmaking = 1;
    	}
        } else {
    	listmaking = 0;
        }
    
        static GLfloat lmambient[] = { 0.1, 0.1, 0.1, 1.0 };
        static GLfloat amblight[] = { 0.1,	0.1, 0.1, 1.0 };
        static GLfloat whitelight[] = { 1, 1, 1, 1 };
        static GLfloat Ce[] = { 0, 0, 0, 1 };
        static GLfloat Ca[] = { 1, 1, 1, 1 };
        static GLfloat Cd[] = { 1, 1, 1, 1 };
        static GLfloat Cs[] = { 1, 1, 1, 1 };
        static GLfloat lightpos[3][4] = {
    		0, 0, -1, 0,
    		1, 0, -.5, 0,
    		0, 1, 0, 0,
        };
    
        glShadeModel( GL_SMOOTH );
    
        glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE );
        glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE );
        glLightModelfv( GL_LIGHT_MODEL_AMBIENT, lmambient );
        glDisable( GL_LIGHTING );
    
        int i;
    
        for(i = 0; i < 8; i++)
    	glDisable( GL_LIGHT0+i );
    
        for(i = 0; i < 3; i++) {
    	glLightfv( GL_LIGHT0+i, GL_AMBIENT, amblight );
    	glLightfv( GL_LIGHT0+i, GL_SPECULAR, whitelight );
    	glLightfv( GL_LIGHT0+i, GL_DIFFUSE, whitelight );
    	glLightfv( GL_LIGHT0+i, GL_POSITION, lightpos[i] );
    	glEnable( GL_LIGHT0+i );
        }
    
    
        glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, Ce );
        glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, Ca );
        glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, Cd );
        glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, Cs );
        glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 32.0 );
    
        glColorMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE );
        glDisable( GL_COLOR_MATERIAL );
    
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA,  GL_ONE_MINUS_SRC_ALPHA );
        glDisable( GL_BLEND );
    
        if(listmaking)
    	glEndList();
    }
    
    PvObject::PvObject() : st_(0), name_(0), id_(-1), parent_(0) {
    }
    
    PvObject::PvObject( const char *name, int id ) {
        this->init( name, id );
    }
    
    void PvObject::init( const char *name, int id ) {
        struct stuff *st = specks_init( 0, NULL );
        st->clk = ppszg.clk;
        this->st_ = st;
        this->To2w( &Tidentity );
        this->name_ = name ? strdup(name) : strdup("");
        this->id_ = id;
        this->parent_ = 0;
    }
    
    void PvObject::draw( bool inpick ) {
    
        if(st_ == NULL)
    	return;
    
        specks_set_timestep( st_ );
        specks_current_frame( st_, st_->sl );
    
        glPushMatrix();
        glMultMatrixf( & To2w_.m[0] );
    
        st_->inpick = (int)inpick;
        drawspecks( st_ );
        st_->inpick = 0;
    
        glPopMatrix();
    }
    
    
    
    void drawetc( const Point *cen, float censize ) {
    
      static Point xyz[3] = {{{1,0,0}}, {{0,1,0}}, {{0,0,1}}};
      static Point nxyz[3] = {{{-1,0,0}}, {{0,-1,0}}, {{0,0,-1}}};
      static Point zero = {{0,0,0}};
    
      static float white[3] = {1,1,1};
      int i;
    
      // if(ppszg.drawtrace)
      //   (*ppszg.drawtrace)();
    
      parti_lighting();
      glDisable(GL_LIGHTING);
      if(censize > 0) {
        glPushMatrix();
        glScalef(censize,censize,censize);
        glBegin(GL_LINES);
        for(i = 0; i < 3; i++) {
    	glColor3fv(xyz[i].x);
    	glVertex3fv(xyz[i].x);
    	glVertex3f(0,0,0);
        }
        glEnd();
        glPopMatrix();
      }
    
      if(censize != 0) {
        censize = fabs(censize);
        glPushMatrix();
        glTranslatef(cen->x[0],cen->x[1],cen->x[2]);
        glScalef(censize, censize, censize);
        glBegin(GL_LINES);
        for(i = 0; i < 3; i++) {
    	glColor3fv(white);
    	glVertex3fv(nxyz[i].x);
    	glVertex3fv(zero.x);
    
    	glColor3fv(xyz[i].x);
    	glVertex3fv(zero.x);
    	glVertex3fv(xyz[i].x);
        }
        glEnd();
        glPopMatrix();
      }
    }
    
    // Draw entire partiview scene.
    // XXX How can we handle background color, ppszg.bgcolor?   Do we glClear() here ??
    //
    // YYY setting "inpick" is part of what's needed to go into OpenGL "selection" mode,
    // YYY but we don't need that immediately if at all.
    //  Just call draw() and let inpick default to false.
    void PvScene::draw( bool inpick ) {
    
        drawetc( center(), censize() );
    
        for(int i = 0; i < nobjs(); i++) {
    	obj(i)->draw( inpick );
        }
    }
    
    PvObject *PvScene::addobj( const char *objname, int id ) {
        if(id < 0)
    	id = objs_.size();
        if(objstuff(id) == 0) {
    	if(id >= objs_.capacity())
    	    objs_.reserve( id + 15 );
    
    	if(id >= objs_.size())
    	    objs_.resize( id+1 );
    
    	objs_[id].init( objname, id );
        } else {
    	msg("Warning: Reusing object g%d (named %s)", id, objname);
        }
        return &objs_[id];
    }
    
    
    // watching file descriptors for external input, e.g. from network.
    // XXX good to implement these someday.
    void parti_asyncfd( int fd ) {
        msg("IGNORING parti_asyncfd(%d)", fd);
    }
    void parti_unasyncfd( int fd ) {
        // XXX
    }
    
    
    typedef enum { REMARK, WARN } MsgLevel;
    
    
    static MsgLevel msglevel = REMARK;
    static int processid = 42;
    
    int vmsg( const char *fmt, va_list args ) {
    
      char str[10240];
    #ifdef HAVE_SNPRINTF
      vsnprintf(str, sizeof(str), fmt, args);
    #else
      vsprintf(str, fmt, args);
    #endif
      // XXX Now do something with str!
    
      fputs(str, stderr);
      fputc('\n', stderr);
    
      if(msglevel == REMARK) {
    
      } else {
    
      if(partiLog) {
          char logfname[128];
          sprintf(logfname, partiLog > 1 ? "parti.%04d.log" : "parti.log", processid);
          FILE *f = fopen(logfname, "a");
          if(f != 0) {
    	  fprintf(f, "[%d]: %s\n", processid, str);
    	  fclose(f);
          }
    
    }
    
    int msg( const char *fmt, ... ) {
      int val;
    
      va_list args;
    
      va_start(args, fmt);
      val = vmsg( fmt, args );
      va_end(args);
      return val;
    }
    
    void warn( const char *fmt, ... ) {
    
      va_list args;
    
    
      va_start(args, fmt);
      vmsg(fmt, args);
      va_end(args);
    }
    
    
    Point Pstandard_head = { 0, 5.0, 3.5 };
    
    
    
    void parti_getc2w( Matrix *c2w ) {
    
      /*
       * Modified July 23, 2008 William Davis
       * We needed the non-inverted matrix,
       * changed from ar_getNavInvMatrix to
       * ar_getNavMatrix();
       */
      arMatrix4 navmat = ar_getNavMatrix();
    
      Matrix cave2w;
      copyfrom_arMatrix( &cave2w, &navmat );
      Matrix Thead2cave = Tidentity;
      Thead2cave.m[3*4+0] = Pstandard_head.x[0];
      Thead2cave.m[3*4+1] = Pstandard_head.x[1];
      Thead2cave.m[3*4+2] = Pstandard_head.x[2];
      mmmul( c2w, &Thead2cave, &cave2w );
    
    void parti_setc2w( const Matrix *Tc2w ) {
    
      //msg("IGNORING parti_setc2w() (\"jump\")");
      //XXX handle the "jump" command.  Something like
      /*
       * Modified July 23, 2008 William Davis
       * Sets the navigation matrix to the coordinates
       * specified by the "jump" command
       * Removed eucinv( &w2c, c2w )
       * Modified copyto_arMatrix() to match
       */
    
      arMatrix4 h2C = ppszg.getMatrix( 0 );	// get head-to-Cave transform
      // Ignore head orientation, just use its position,
      // and take shortcut rather than eucinv for inverse.
      Matrix invTh2C = Tidentity;
    
    slevy's avatar
    slevy committed
    #if 0
    
      invTh2C.m[12] = -h2C.v[12];
      invTh2C.m[13] = -h2C.v[13];
      invTh2C.m[14] = -h2C.v[14];
    
    slevy's avatar
    slevy committed
    #else
    
      invTh2C.m[12] = -Pstandard_head.x[0];
      invTh2C.m[13] = -Pstandard_head.x[1];
      invTh2C.m[14] = -Pstandard_head.x[2];
    
    slevy's avatar
    slevy committed
    #endif
    
    
      Matrix Thead2world;
      mmmul( &Thead2world, &invTh2C, Tc2w );
      arMatrix4 jumpto;
      copyto_arMatrix( &jumpto, &Thead2world );
    
    
      const arHead *hd = ppszg.getHead();
      arVector3 eyeL = hd->getEyePosition( -1 );
      arVector3 eyeR = hd->getEyePosition( +1 );
    
      arMatrix4 wasat = ar_getNavMatrix();
    
      ar_setNavMatrix( jumpto );
    
      arMatrix4 nowat = ar_getNavMatrix();
      arMatrix4 hdmat = ppszg.getMatrix( 0 );
    
    
      msg("eyes:  %.3f %.3f %.3f  %.3f %.3f %.3f", eyeL.v[0],eyeL.v[1],eyeL.v[2], eyeR.v[0],eyeR.v[1],eyeR.v[2]);
    
      msg("head:   %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f",
    	  hdmat.v[0],hdmat.v[1],hdmat.v[2],hdmat.v[3],
    	  hdmat.v[4],hdmat.v[5],hdmat.v[6],hdmat.v[7],
    	  hdmat.v[8],hdmat.v[9],hdmat.v[10],hdmat.v[11],
    	  hdmat.v[12],hdmat.v[13],hdmat.v[14],hdmat.v[15]);
      msg("navwas: %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f",
    	  wasat.v[0],wasat.v[1],wasat.v[2],wasat.v[3],
    	  wasat.v[4],wasat.v[5],wasat.v[6],wasat.v[7],
    	  wasat.v[8],wasat.v[9],wasat.v[10],wasat.v[11],
    	  wasat.v[12],wasat.v[13],wasat.v[14],wasat.v[15]);
      msg("setc2w: %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f",
    
    	  Tc2w->m[0],Tc2w->m[1],Tc2w->m[2],Tc2w->m[3],
    	  Tc2w->m[4],Tc2w->m[5],Tc2w->m[6],Tc2w->m[7],
    	  Tc2w->m[8],Tc2w->m[9],Tc2w->m[10],Tc2w->m[11],
    	  Tc2w->m[12],Tc2w->m[13],Tc2w->m[14],Tc2w->m[15]);
    
      msg("navnow: %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f  %.3f %.3f %.3f %.3f",
    	  nowat.v[0],nowat.v[1],nowat.v[2],nowat.v[3],
    	  nowat.v[4],nowat.v[5],nowat.v[6],nowat.v[7],
    	  nowat.v[8],nowat.v[9],nowat.v[10],nowat.v[11],
    	  nowat.v[12],nowat.v[13],nowat.v[14],nowat.v[15]);
    
    }
    
    double syzygy_time() {	// called from sclock.c
    
    }
    
    // Camera Animation (from .wf path)
    static void playidle( void * ) {
      struct wfpath *path = &ppszg.path;
    
      if(!ppszg.playing) {
        // XXX disable calling playidle() in this case. // Fl::remove_idle( playidle, NULL );
        ppszg.playidling = 0;
        return;
      }
    
    
      double now;
    #if TESTCAVE
      now = ppszg.getTime();
    #else 
      now = wallclock_time();
    #endif
    
      
      if(ppszg.playspeed == 0)
        ppszg.playspeed = 1;
    
      int frame = ppszg.framebase;
    
      if(ppszg.playevery) {
        frame += (int) ((ppszg.playspeed < 0)	/* round away from zero */
    		    ? ppszg.playspeed - .999f
    		    : ppszg.playspeed + .999f);
        ppszg.framebase = frame;
        ppszg.playtimebase = now;
      } else {
        frame += (int) ((now - ppszg.playtimebase) * ppszg.playspeed * path->fps);
      }
    
      if(frame < path->frame0 || frame >= path->frame0 + path->nframes) {
    
        frame = (frame < path->frame0)
    	  ? path->frame0
    	  : path->frame0 + path->nframes-1;
    
        switch(ppszg.playloop) {
        case 0:	/* Not looping; just stop at end. */
    	// XXX disable calling playidle() here   // Fl::remove_idle( playidle, NULL );
    	ppszg.playidling = 0;
    
    	//parti_play( "stop" );
    
    	break;
    
        case -1:	/* Bounce: stick at same end of range, change direction */
    	ppszg.playspeed = -ppszg.playspeed;
    	break;
    
        case 1:	/* Loop: jump to other end of range */
    	frame = (frame == path->frame0)
    		? path->frame0 + path->nframes-1
    		: path->frame0;
        }
        ppszg.framebase = frame;
        ppszg.playtimebase = now;
        
        parti_setframe( frame );
        return;
      }
    
      /*
       * Cases:
       *  - we're displaying frames slower than real time.
       *	=> use an idle function.
       *  - we're displaying frames faster than real time.
       *    => use a timeout.
       */
    
      int needidle = ppszg.playidling;
      float dt = 0;
    
      if(frame != path->curframe) {
        parti_setframe( frame );
        needidle = 1;
      } else {
        frame += (ppszg.playspeed > 0) ? 1 : -1;
        dt = (frame - ppszg.framebase) / (ppszg.playspeed * path->fps)
    	+ ppszg.playtimebase - now;
        needidle = (dt < .05);
      }
    
      if(needidle && !ppszg.playidling) {
        // XXX enable calling playidle() here  // Fl::add_idle( playidle, NULL );
    
        ppszg.playidling = 1;
    
      }
      if(!needidle && ppszg.playidling) {
        // XXX disable calling playidle() here // Fl::remove_idle( playidle, NULL );
    
        ppszg.playidling = 0;
    
      }
      ppszg.playidling = needidle;
    
      if(!needidle) {
        // XXX schedule calling playidle once, "dt" seconds from now // Fl::add_timeout( dt, playidle, NULL );
      }
    }
    
    
    
    void parti_play( const char *rate ) {
      struct wfpath *path = &ppszg.path;
      int playnow = 1;
      int awaitdone = 0;
    
      msg("Inside the parti_play!");
      msg(rate);
    
      if(rate != NULL) {
        char *ep;
        float sp = strtod(rate, &ep);
    
        printf("Rate: %f\n", sp);
    
        if(strchr(ep, 's')) playnow = 0;
        if(strchr(ep, 'l')) ppszg.playloop = 1;
        else if(strchr(ep, 'k')) ppszg.playloop = -1; /* rock */
        else if(strchr(ep, 'f') || strchr(ep, 'e')) ppszg.playevery = 1;
        else if(strchr(ep, 'r') || strchr(ep, 't')) ppszg.playevery = 0;
        if(strstr(rate, "wait"))
    	awaitdone = 1;
    
        if(ep == rate) {
    	/* No leading number -- did we get just "-" or "-?" or "-help"? */
    	if(0==strcmp(rate, "-") || strchr(rate, '?') || strchr(rate, 'h')) {
    	    msg("Usage: play [rate][s][l|k][f|r][wait]  e.g.  \"play\" or \"play 30\" or \"play 10kf wait\"");
    	    msg("   rate  frames/sec; [s]et speed, don't play now; [l]oop/roc[k]");
    	    msg("         play every [f]rame/skip to maintain [r]ate;  [wait] until done");
    	    return;
    	}
    	if(sp == 0) playnow = 0;
    	else ppszg.playspeed = sp;
        }
      }
    
      // Disable playidle() idle-function
      // Fl::remove_idle( playidle, NULL );
      // Fl::remove_timeout( playidle, NULL );
      ppszg.playing = 0;
      ppszg.playidling = 0;
    
      if(playnow) {
    
    #if CAVE
        ppszg.playtimebase = .001 * ppszg.getTime();
    #else
    
        ppszg.playtimebase = wallclock_time();
    
    #endif
        printf("playtimebase: %f", ppszg.playtimebase);
    
        ppszg.framebase = path->curframe;
        if(ppszg.playspeed > 0 && path->curframe >= path->frame0+path->nframes-1)
    	ppszg.framebase = path->frame0;
        else if(ppszg.playspeed < 0 && path->curframe <= path->frame0)
    	ppszg.framebase = path->frame0 + path->nframes - 1;
    
        parti_setframe( ppszg.framebase );
    
        /* Switch into "play" mode */
        // XXX start calling playidle() idle-function 
    
        ppszg.playidling = 1;
    
        ppszg.playing = 1;
      }
    
    }
    
    
    // Idle function -- enabled when data-animation clock is running
    void pp_stepper() {
      if(ppszg.st) {
        clock_tick( ppszg.st->clk );
        specks_set_timestep( ppszg.st );
      }
    }
    
    // Data animation
    void parti_set_running( struct stuff *st, int on ) {
        if(clock_running(st->clk) != on) {
    	if(on) {
    	    // XXX enable pp_stepper idle function
    	} else {
    	    // XXX disable pp_stepper idle function, no longer needed (?)
    	}
    	if(st->clk) st->clk->walltimed = 1;
    	clock_set_running(st->clk, on);
        }
    }
    
    void readrc( struct stuff **stp ) {
      char *rc = findfile( NULL, "~/.partiviewrc" );
      if(rc) specks_read( stp, rc );
      rc = findfile( NULL, "./.partiviewrc" );
      if(rc) specks_read( stp, rc );
    }
    
    static int cmdargs(int argc, char **argv, int & optind) {
      char *arg = argv[optind];
      if(!strcmp("-c", arg)) {
        arg = argv[optind+1];
        char *t = NewN(char, strlen(arg)+1);
        char *av[128];
        int ac = tokenize(arg, t, 128, av, NULL);
        specks_parse_args( &ppszg.st, ac, av );
        Free(t);
        optind += 2;
        return 1;
      }
    
      return 0;
    }
    
    int pp_parse_args( struct stuff **, int argc, char *argv[], char *fromfname, void * ) {
      if(argc < 1) return 0;
    
      int i;
      if(!strcmp( argv[0], "exit" )) {
    
            msg("exiting");
          	// ppszg.stop( 1 );
    	ppszg.quitnow_ = 1;
    
      } else if(!strcmp( argv[0], "fly" )) {
          if(argc > 1)
    	  flyer.config( argv[1] );
          msg("fly %s  povbutton %d centerbutton %d tspeed %g rspeed %g  (\"fly %d,%d,%g,%g\"",
    	     flyer.flying ? "on" : "off",
    	    flyer.bpov, flyer.bcen, flyer.tspeed, flyer.rspeed, 
    	    flyer.bpov, flyer.bcen, flyer.tspeed, flyer.rspeed);
    
    
      } else if(!strcmp( argv[0], "stereo" )) {
    	for(i = 1; i < argc; i++)
    	    parti_stereo( argv[i] );
    
    	msg("stereo %s", parti_stereo(NULL));
    
      } else if(!strcmp( argv[0], "winsize" )) {
    	msg("winsize %s", parti_winsize( rejoinargs( 1, argc, argv ) ) );
    
      } else if(!strncmp( argv[0], "snapset", 7 ) || !strcmp( argv[0], "snapshot" )) {
    	char *frameno = NULL, *basename = NULL;
    	char *size = NULL;
    	int now = (0 == strcmp(argv[0], "snapshot"));
    	while(argc > 2) {
    	    if(!strcmp(argv[1], "-w")) {
    		size = argv[2];
    	    } else if(!strcmp(argv[1], "-n")) {
    		frameno = argv[2];
    	    } else
    		break;
    	    argc -= 2, argv += 2;
    	}
    	if(argc > 1)
    	    basename = rejoinargs( 1, argc, argv );
    	parti_snapset( basename, frameno, size );
    	if(now) {
    	    char snapinfo[1024]; 
    	    if(parti_snapshot(snapinfo) >= 0)
    		msg("Snapped %s", snapinfo);
    	}
    
      } else if(!strcmp( argv[0], "clip" )) {
    	msg("%s", parti_clip( argv[1], argc>2?argv[2]:NULL ) );
    
      } else if(!strcmp( argv[0], "pickrange" )) {
    	float pickrange = parti_pickrange( argv[1] );
    	msg("pickrange %g", parti_pickrange( NULL ));
    
      } else if(!strcmp( argv[0], "fov" ) || !strcmp( argv[0], "fovy" )) {
    	float fovy = parti_fovy( (argc>1) ? argv[1] : NULL );
    	msg("fovy %g", fovy);
    
      } else if(!strcmp( argv[0], "subcam")) {
    	int index;
    	if(argc > 1) {
    	    if(!strcmp(argv[1], "-") || !strcmp(argv[1], "off")) {
    		parti_select_subcam(0);
    	    } else {
    		index = parti_make_subcam( argv[1], argc-2, argv+2 );
    		if(index > 0) {
    		    parti_select_subcam(index);
    		} else {
    		    char *what = parti_subcam_list();
    		    msg(what[0]=='\0' ?  "No subcams defined yet" :
    					"Known subcams: %s", what);
    		}
    	    }
    	}
    	index = parti_current_subcam();
    	if(index > 0) {
    	    char params[100];
    	    char *name = parti_get_subcam( index, params );
    	    msg("subcam %s  %s # az el rol  L R B T", name, params);
    	} else {
    	    int wx, wy;
    	    float hx, hy = .5 * parti_fovy(NULL);
    	    sscanf( parti_winsize(NULL), "%d%*c%d", &wx, &wy );
    	    if(wy == 0) wy = 1;
    	    hx = atan( tan(hy*(M_PI/180))*wx / wy ) * (180/M_PI);
    	    msg("subcam off  0 0 0  %g %g  %g %g # a e r   L R B T (default)",
    			hx, hx, hy, hy);
    	}
    
      } else if(!strncmp( argv[0], "focallen", 8 )) {
    	float focallen = parti_focal( (argc>1) ? argv[1] : NULL );