Skip to content
Snippets Groups Projects
partibrains.c 147 KiB
Newer Older
teuben's avatar
teuben committed

#define __USE_MISC	/* makes <math.h> define sqrtf() on GNU libc */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>

#if !sgi && !defined(sqrtf)
# define sqrtf(x)  sqrt(x)	/* if no sqrtf() */
#endif

#undef isspace		/* hack for irix 6.5 back-compat */
#undef isdigit
#undef isalnum

#if unix
# include <unistd.h>
# include <sys/types.h>
# include <netinet/in.h>  /* for htonl */
slevy's avatar
 
slevy committed
# include <time.h>
teuben's avatar
teuben committed
# include <alloca.h>
slevy's avatar
 
slevy committed
# ifndef WORDS_BIGENDIAN
#  include "config.h"	/* for WORDS_BIGENDIAN */
# endif
teuben's avatar
teuben committed
#else /*WIN32*/
# include "winjunk.h"
slevy's avatar
 
slevy committed
# define WORDS_BIGENDIAN 0
teuben's avatar
teuben committed
#endif
#include <string.h>
#include <errno.h>

slevy's avatar
 
slevy committed

slevy's avatar
 
slevy committed
#if WORDS_BIGENDIAN
teuben's avatar
teuben committed
#define RGBALPHA(rgb, alpha)	((rgb) | (alpha))
#define RGBWHITE		0xFFFFFF00
#define	PACKRGBA(r,g,b,a)	((r)<<24 | (g)<<16 | (b)<<8 | (a))
#define	THRESHBIT		PACKRGBA(0,0,0,1)
#else
#define RGBALPHA(rgb, alpha)	((rgb) | ((alpha)<<24))
#define RGBWHITE		0x00FFFFFF
#define	PACKRGBA(r,g,b,a)	((a)<<24 | (b)<<16 | (g)<<8 | (r))
#define	THRESHBIT		PACKRGBA(0,0,0,1)
#endif

#if CAVE
# define  CAVEMENU  1
#endif

#include "geometry.h"
#include <GL/gl.h>

#if CAVEMENU
#include <cave_ogl.h>
#include "cavemenu.h"
#include "vd_util.h"
#define IFMENU(x)  (x)
#else
#define IFMENU(x)
#endif

#include "shmem.h"	/* NewN(), etc. */
#include "futil.h"

#include "specks.h"

#include "textures.h"
#include "findfile.h"
#include "partiviewc.h"
#include "sfont.h"

#include <sys/types.h>
#include <signal.h>

#ifdef sgi
#include <malloc.h>	/* for mallinfo(), amallinfo() */
teuben's avatar
teuben committed
#endif

#ifdef USE_KIRA
#include "kira_parti.h"
teuben's avatar
teuben committed
#endif

  /* Star Renderer (.sdb) structure -- from stardef.h */
typedef enum {ST_POINT, ST_BRIGHT_CLOUD ,ST_DARK_CLOUD, ST_BOTH_CLOUD, OFF} stype;

typedef struct {
        float  x, y, z;
        float  dx, dy, dz;
        float  magnitude, radius;
        float  opacity;
        int  num;
        unsigned short  color;
        unsigned char   group;
        unsigned char   type;
} db_star;

typedef  struct  hrec { float  t;  int  num;}  hrec_t;
typedef  struct  mrec { float  mass, x, y, z, vx, vy, vz, rho, temp, sfr, gasmass;
                 int  id, token;}  mrec_t;
  /* end Star Renderer */


#define VDOT( v1, v2 )  ( (v1)->x[0]*(v2)->x[0] + (v1)->x[1]*(v2)->x[1] + (v1)->x[2]*(v2)->x[2] )

#if CAVEMENU
int parti_menuwall, parti_menubox[4];

static MenuHead *pmenu = NULL;
static MenuHead *stubmenu = NULL;

struct ments {
  MenuEnt *alpha, *point, *poly, *label, *psize, *polysize, *fog;
  MenuEnt *step, *fwd, *lumvar, *slum, *colorvar, *where[4];
  MenuEnt *viewall, *setscale, *speed, *fspeed, *every;
  MenuEnt *seedata, *box, *boxlevel[12];
  MenuEnt *annot;
  MenuEnt *tknob[3];
  MenuEnt *menu;
} ment;

struct boxleveler {
  struct stuff *st;
  int level;
};

void set_tknob( float val, MenuEnt *me, void *st );
void set_psize( float psize, MenuEnt *me, void *st );
void set_polysize( float psize, MenuEnt *me, void *st );
void set_alpha( float alpha, MenuEnt *me, void *st );
void set_point( int on, MenuEnt *me, void *st );
void set_poly( int on, MenuEnt *me, void *st );
void set_label( int on, MenuEnt *me, void *st );

void set_step( float time0, MenuEnt *me, void *st );
void set_fwd( int fwd, MenuEnt *me, void *st );
void set_slum( float slum, MenuEnt *me, void *st );
void set_lumvar( int which, MenuEnt *me, void *st );
void set_colorvar( int which, MenuEnt *me, void *st );
void set_seedata( int which, MenuEnt *me, void *st );
void set_speed( float speed, MenuEnt *me, void *st );
void set_fspeed( float fspeed, MenuEnt *me, void *st );
void set_every( float every, MenuEnt *me, void *st );
void set_scale( float logscale, MenuEnt *me, void *st );
void set_viewall( int all, MenuEnt *me, void *st );
void set_allboxes( int on, MenuEnt *me, void *st );
void set_boxlevel( int on, MenuEnt *me, void *bleveler );
void set_where( int unused, MenuEnt *me, void *st );
void set_menu( int on, MenuEnt *me, void *st );
#endif /*CAVEMENU*/

static int defcmap[] = {
  0x11eeee00,
  0x1106ee00, 0x120ea900, 0x1316ce00, 0x1520d500, 0x172ece00, 0x193fcb00,
  0x1c54b400, 0x206da400, 0x24889200, 0x2aa58400, 0x33c07600, 0x3ed96a00,
  0x4eed6100, 0x63fa5a00, 0x7efe5100, 0x99fb4c00, 0xafef4700, 0xc0dc4000,
  0xcbc33900, 0xd4a83000, 0xda8b2900, 0xdf702700, 0xe2572500, 0xe5411700,
  0xe82f1000, 0xea211500, 0xeb171400, 0xed0f1200, 0xee0aee00,
  0xffffff00,
};

int orientboxcolor = PACKRGBA( 0xff, 0xff, 0, 0xff );

void specks_read( struct stuff **stp, char *fname );
void specks_read_boxes( struct stuff *st, char *fname, int timestep );
int  specks_add_box( struct stuff *st, struct AMRbox *box, int timestep );
int  specks_purge( void *vstuff, int nbytes, void *aarena );
int  specks_count( struct specklist *head );
int  specks_gobox( struct stuff *st, int boxno, int argcrest, char *argvrest[] );

slevy's avatar
 
slevy committed
void specks_reupdate( struct stuff *st, struct specklist *sl );
teuben's avatar
teuben committed
struct specklist **specks_find_annotation( struct stuff *, struct specklist **);
void specks_set_current_annotation( struct stuff *st, char *annotation );
void specks_add_annotation( struct stuff *st, char *annotation, int timestep );
slevy's avatar
 
slevy committed
void specks_timerange( struct stuff *st, double *tminp, double *tmaxp );
teuben's avatar
teuben committed

extern int getbool( char *str, int defval );
slevy's avatar
 
slevy committed
extern double getfloat( char *str, double defval );
teuben's avatar
teuben committed

static char separ[] = " \t\n";

void strncpyt( char *dst, char *src, int dstsize ) {
  int len = strlen(src);
  if(len >= dstsize) len = dstsize-1;
  memcpy(dst, src, len);
  dst[len] = '\0';
} 

#ifdef sgi
static float defgamma = 1.0;
#else
static float defgamma = 2.5;
#endif

struct stuff *
specks_init( int argc, char *argv[] )
{
  int i;
  struct stuff *st = NewN( struct stuff, 1 );
  char *menuopt;

teuben's avatar
teuben committed
  memset(st, 0, sizeof(*st));
  st->spacescale = 1.0;
  st->fog = 0;
  st->psize = 1;
  st->alpha = .5;
  st->gamma = defgamma;
  st->useme = 1;
  st->usepoly = 0;
  st->usepoint = 1;
  st->usetext = 1;
  st->usetextaxes = 1;
  st->usetextures = 1;
  st->useboxes = 1;
  st->polysizevar = -1;
  st->polyarea = 0;
  st->polyorivar0 = -1;
  st->texturevar = -1;
  st->txscale = .5;
  st->boxlabels = 0;
  st->boxlabelscale = 1.0;
  st->boxlevelmask = ~0;	/* all levels on */
  st->boxaxes = 0;		/* boxes don't show orientation markers */
  st->goboxscale = 1.0;
  st->textsize = .05;
  st->npolygon = 11;
  st->subsample = 1;
slevy's avatar
 
slevy committed
  st->everycomp = 1;
teuben's avatar
teuben committed

  st->menudemandfps = 4.0;

  st->pfaint = .05;	/* params for "fast" point-drawing */
  st->plarge = 10;
  st->polymin = .5;	/* don't draw polygons if smaller (pixels) */
  st->polymax = 2048;	/* don't allow polygons to get bigger than this (pixels) */
  st->textmin = 2;	/* replace labels with line-segments if smaller (pixels) */
  st->ntextures = 0;
  st->textures = NULL;

  st->fade = F_SPHERICAL;
  st->fadeknee1 = 10.0;
  st->fadeknee2 = 1.0;
  st->knee2steep = 1.0;

  st->gscale = 1.;
  st->gtrans.x[0] = st->gtrans.x[1] = st->gtrans.x[2] = 0;
  st->objTo2w = Tidentity;

  st->ncmap = st->boxncmap = COUNT(defcmap);
  st->cmap = NewN(int, COUNT(defcmap));
  st->boxcmap = NewN(int, COUNT(defcmap));
  for(i = 0; i < COUNT(defcmap); i++)
    st->cmap[i] = st->boxcmap[i] = htonl(defcmap[i]);

  st->sizedby = 0;
  st->coloredby = 1;
  st->sizeseq = st->colorseq = st->threshseq = 0;
  st->trueradius = 0;
  st->sdbvars = shmstrdup( "mcr" );

slevy's avatar
 
slevy committed
  st->nghosts = 0;

teuben's avatar
teuben committed
  st->ntimes = 0;
  st->ndata = 0;
  st->curtime = 0;
  st->curdata = 0;
  st->datatime = 0;
slevy's avatar
 
slevy committed
  st->usertrange = 0;
  st->utmin = -HUGE;
  st->utmax = HUGE;
  st->utwrap = 0.1;
teuben's avatar
teuben committed
  st->sl = NULL;

  st->boxes = NULL;
  st->boxlevels = 0;
  st->boxlinewidth = 0.75;

slevy's avatar
 
slevy committed
  st->depthsort = 0;

slevy's avatar
 
slevy committed
  st->clk = NewN(SClock, 1);
  clock_init(st->clk);
  clock_set_running(st->clk, 1);
teuben's avatar
teuben committed

  memset(st->anima, 0, sizeof(st->anima));
  memset(st->annot, 0, sizeof(st->annot));
  memset(st->datafile, 0, sizeof(st->datafile));
  memset(st->fname, 0, sizeof(st->fname));


#if CAVE
  shmrecycler( specks_purge, st );
#endif

  for(i = 1; i < argc; i++)
    specks_read( &st, argv[i] );

#if CAVEMENU
  menu_preinit();

  pmenu = menu_create( .05, .95 );
  menu_setfont( pmenu, font_small );

  ment.annot = menu_addentry( pmenu, " ", NULL, NULL );
  menu_addentry( pmenu, " ", NULL, NULL );

  menuopt = getenv("PARTIMENU");
  if(menuopt == NULL) menuopt = "label";

  if(strstr(menuopt, "survey")) {	/* SC99DEMO star-formation survey */
    for(i = 0; i < 3; i++) {
	struct boxleveler *tb = NewN( struct boxleveler, 1 );
	tb->st = st;
	tb->level = i;
	ment.tknob[i] = menu_addknob( pmenu, NULL, set_tknob, 0, 0,4, tb );
    }
  }
  menu_addentry( pmenu, " ", NULL, NULL );

  ment.psize = menu_addknob( pmenu, NULL, set_psize, st->psize, 0.1,20, st );
  ment.slum = menu_addknob( pmenu, NULL, set_slum,
		st->vdesc[st->curdata][st->sizedby].lum, .1, 10, st );
  ment.lumvar = menu_addtoggle( pmenu, NULL, set_lumvar, st->sizedby, MAXVAL, st );
  ment.colorvar = menu_addtoggle( pmenu, NULL, set_colorvar, st->coloredby, MAXVAL, st );
  ment.seedata = menu_addtoggle( pmenu, NULL, set_seedata, st->curdata, 2, st );
  if(strstr(menuopt, "label"))
    ment.label = menu_addtoggle( pmenu, NULL, set_label, st->usetext, 2, st );
  if(strstr(menuopt, "poly"))
    ment.poly = menu_addtoggle( pmenu, NULL, set_poly, st->usepoly, 2, st );
  menu_addentry( pmenu, " ", NULL, NULL );	/* spacer */

#ifndef STATIC_SPECKS
slevy's avatar
 
slevy committed
  ment.fspeed = menu_addknob( pmenu, NULL, set_fspeed, clock_speed(st->clk), 0, 5, st );
  ment.speed = menu_addknob( pmenu, NULL, set_speed, clock_speed(st->clk), .5, 5, st );
  ment.step = menu_addknob( pmenu, NULL, set_step, clock_time(st->clk), 0, 10, st );
  ment.fwd = menu_addtoggle( pmenu, NULL, set_fwd, clock_fwd(st->clk), 2, st );
teuben's avatar
teuben committed
#endif

#ifndef NO_WHERE
  ment.where[0] = menu_addentry( pmenu, "", NULL, NULL );
  ment.where[1] = menu_addentry( pmenu, "", NULL, NULL );
  ment.where[2] = menu_addentry( pmenu, "", NULL, NULL );
  ment.where[3] = menu_addentry( pmenu, "", NULL, NULL );
#endif

  ment.every = menu_addknob( pmenu, NULL, set_every, st->subsample, 1, 10, st );

  menu_addentry( pmenu, " ", NULL, NULL );

  ment.setscale = menu_addknob( pmenu, NULL, set_scale, 0., -4., 1., st );
  if(strstr(menuopt, "viewall"))
    ment.viewall = menu_addtoggle( pmenu, NULL, set_viewall, 0, 2, st );

  menu_addentry( pmenu, " ", NULL, NULL );

  ment.box = menu_addtoggle( pmenu, NULL, set_allboxes, st->useboxes, 3, st );
  menu_addentry( pmenu, " ", NULL, NULL );
  {
    struct boxleveler *blev = NewN( struct boxleveler, COUNT(ment.boxlevel) );
    for(i = 0; i < COUNT(ment.boxlevel); i++) {
	blev[i].st = st;
	blev[i].level = i;
	ment.boxlevel[i] = menu_addtoggle( pmenu, NULL, set_boxlevel, 1, 2,
		&blev[i] );
    }
  }

  ment.menu = menu_addtoggle( pmenu, "<Menu>", set_menu, 0, 2, st );
  menu_addentry( pmenu, " ", NULL, NULL );


  stubmenu = menu_create( .05, .15 );
  menu_addtoggle( stubmenu, "<Menu>", set_menu, 0, 2, st );
  menu_sethidden( stubmenu, 1 );
#endif /*NOCAVE*/

  return st;
}

#if CAVEMENU
void specks_refresh_menu(struct stuff *st)
{
  int i;
slevy's avatar
 
slevy committed
  int empty = st->ntimes == 0 && st->sl == NULL;
teuben's avatar
teuben committed

#if MENU_IN_PPR
  menu_check( pmenu, pmenu->cavewall );
  menu_check( stubmenu, stubmenu->cavewall );

#endif
slevy's avatar
 
slevy committed
  menu_sethidden( pmenu, st->hidemenu || empty );
  menu_sethidden( stubmenu, st->hidemenu<=0 || empty );
teuben's avatar
teuben committed

  if(st->vdcmd[0] != '\0') {
    VIDI_queue_commandstr( st->vdcmd );
    st->vdcmd[0] = '\0';
  }
  set_psize( st->psize, ment.psize, st );
  set_lumvar( st->sizedby, ment.lumvar, st );
  set_colorvar( st->coloredby, ment.colorvar, st );
  set_slum( st->vdesc[st->curdata][st->sizedby].lum, ment.slum, st );
  set_every( st->subsample, ment.every, st );
  set_scale( ment.setscale->val, ment.setscale, st );
  if(ment.label) set_label( st->usetext, ment.label, st );
  if(ment.poly) set_poly( st->usepoly, ment.poly, st );
  if(ment.annot)
    menu_settitle( ment.annot, st->annotation ? st->annotation : " " );
  if(st->ntimes > 1) {
    menu_setknobrange( ment.step, 0, st->ntimes-1 );
    set_fwd( st->timefwd, ment.fwd, st );
    set_step( st->time0, ment.step, st );
slevy's avatar
 
slevy committed
    set_speed( clock_speed(st->clk), ment.speed, st );
teuben's avatar
teuben committed
  } else {
    menu_settitle( ment.fwd, "" );
    menu_settitle( ment.step, "" );
    menu_settitle( ment.speed, "" );
    if(ment.viewall)
      menu_settitle( ment.viewall, "" ); /* no need to "view peak", right? */
  }
  if(st->ndata > 1) {
    set_seedata( st->curdata, ment.seedata, st );
  } else {
    menu_settitle( ment.seedata, "" );
  }
  if(st->boxlevels > 0) {
    set_allboxes( st->useboxes, ment.box, st );
  } else {
    menu_settitle( ment.box, "" );
  }

  if(ment.tknob[0])	/* if SC99DEMO, i.e. if PARTIMENU includes "survey" */
    for(i = 0; i < 3; i++)
      set_tknob( ment.tknob[i]->val, ment.tknob[i], ment.tknob[i]->data );


#ifndef NO_WHERE
  for(i = 0; i < COUNT(ment.where) && ment.where[i] != NULL; i++)
    set_where( i, ment.where[i], st );
#endif

  /* Publicize our menu location so other apps -- Matt's vtk AMR code --
   * can avoid interacting if user's wand points there.
   */
  parti_menuwall = pmenu->cavewall;

  parti_menubox[0] = pmenu->x0 * pmenu->wallxpix;
  parti_menubox[1] = ((pmenu->y0 < pmenu->y1) ? pmenu->y0 : pmenu->y1)
			* pmenu->wallypix;

  parti_menubox[2] = pmenu->x1 * pmenu->wallxpix;
  parti_menubox[3] = ((pmenu->y0 > pmenu->y1) ? pmenu->y0 : pmenu->y1)
			* pmenu->wallypix;

}

void specks_evoke_menu( struct stuff *st ) {
  menu_evoke( pmenu );	/* evoke any callbacks */
}

#endif /*CAVEMENU*/

void specks_rethresh( struct stuff *st, struct specklist *sl, int by )
{
  struct valdesc *vd;
  int i;
  int curdata = st->curdata;
  int nel = sl->nspecks;
  float cmin, cmax, normal;
  int ncmap = st->ncmap;
  int index;
  int min, max;
  float threshmin = st->thresh[0], threshmax = st->thresh[1];
  struct speck *p = sl->specks;

  sl->threshseq = st->threshseq;

  if(sl->text != NULL)	/* specklists with labels shouldn't be thresholded */
    return;

  if(curdata >= st->ndata)
    curdata = 0;

  min = (SMALLSPECKSIZE(by)>=sl->bytesperspeck) ? 0 : st->usethresh&P_THRESHMIN;
  max = (SMALLSPECKSIZE(by)>=sl->bytesperspeck) ? 0 : st->usethresh&P_THRESHMAX;

  for(i = 0, p = sl->specks; i < sl->nspecks; i++, p = NextSpeck( p, sl, 1 )) {
    p->rgba = (min&&p->val[by]<threshmin) || (max&&p->val[by]>threshmax)
	     ? p->rgba | THRESHBIT : p->rgba & ~THRESHBIT;
  }
}

void specks_recolor( struct stuff *st, struct specklist *sl, int by )
{
  struct valdesc *vd;
  int i;
  int curdata = st->curdata;
  int nel = sl->nspecks;
  struct speck *sp = sl->specks;
  float cmin, cmax, normal;
  int ncmap = st->ncmap;
  int index;
  int rgb565;

  sl->coloredby = by;
  sl->colorseq = st->colorseq;

  if(sl->text != NULL)	/* specklists with labels shouldn't be recolored */
    return;

  if(curdata >= st->ndata)
    curdata = 0;

  if(by == CONSTVAL) {
    /* Hack -- color by given RGB value */
    char crgba[4];
    int rgba;
    vd = &st->vdesc[curdata][CONSTVAL];
    crgba[0] = 255 * vd->cmin;
    crgba[1] = 255 * vd->cmax;
    crgba[2] = 255 * vd->mean;
    crgba[3] = 0;
    rgba = *(int *)&crgba[0];
    for(i = 0; i < nel; i++, sp = NextSpeck(sp, sl, 1)) {
	sp->rgba = rgba | (sp->rgba & THRESHBIT);
    }
    return; 
  }

  if(by >= MAXVAL || by < 0 || SMALLSPECKSIZE(by) > sl->bytesperspeck)
	by = 0;
	
  vd = &st->vdesc[curdata][by];


  cmin = vd->cmin, cmax = vd->cmax;
  if(cmin == cmax && cmin == 0 || (vd->call && !vd->cexact)) {
    vd->cmin = cmin = vd->min;
    vd->cmax = cmax = vd->max;
  }

  normal = (cmax != cmin && ncmap>2) ? (ncmap-2)/(cmax - cmin) : 0;
  rgb565 = !strcmp(vd->name, "rgb565") || !strcmp(vd->name, "colors565");

  /* cexact field means:
   *   0 (default): scale data range to cmap index 1..ncmap-2;
   *		    use 0 and ncmap-1 for low- and high- out-of-range values.
   *
   *   1 ("exact"): use data value as literal colormap index, 0..ncmap-1.
   */

  for(i = 0; i < nel; i++, sp = NextSpeck(sp, sl, 1)) {
    if(rgb565) {
	index = sp->val[by];
	sp->rgba = PACKRGBA(
			(index&0xF800)>>(11-(8-5)),
			(index&0x07E0)>>(5-(8-6)),
			(index&0x1F)<<(8-5), 0 )
		  | (sp->rgba & THRESHBIT);
    } else {
	index = vd->cexact  ?   sp->val[by] + cmin
			    :  (sp->val[by] - cmin) * normal + 1;

	if(index < 0) index = 0;
	else if(index >= ncmap) index = ncmap-1;

	sp->rgba = st->cmap[index] | (sp->rgba & THRESHBIT);
    }
  }
}

void specks_resize( struct stuff *st, struct specklist *sl, int by )
{
  int i;
  int nel = sl->nspecks;
  struct speck *sp = sl->specks;
  int curdata = st->curdata;
  struct valdesc *vd;
  float lmin, lmax, normal;

  sl->sizedby = by;
  sl->sizeseq = st->sizeseq;

  if(sl->text != NULL) /* specklists with labels shouldn't be resized */
    return;

  if(curdata < 0 || curdata >= st->ndata)
    curdata = 0;

  if(by == CONSTVAL) {
    vd = &st->vdesc[curdata][CONSTVAL];
    for(i = 0; i < nel; i++, sp = NextSpeck(sp, sl, 1))
	sp->size = vd->lmin;
    return;
  }

  if(by < 0 || by >= MAXVAL)
    by = 0;

  vd = &st->vdesc[curdata][by];

  lmin = vd->lmin, lmax = vd->lmax;
  if(lmin == lmax && lmin == 0 || vd->lall) {
	vd->lmin = lmin = vd->min;
	vd->lmax = lmax = vd->max;
  }

  if(lmax == lmin)
    normal = 1;
  else
    normal = 1 / (lmax - lmin);

  for(i = 0; i < nel; i++, sp = NextSpeck(sp, sl, 1))
    sp->size = (sp->val[by] - lmin) * normal;
}

void specks_datawait(struct stuff *st) {
#if USE_IEEEIO
    while(st->fetching && st->fetchpid > 0
	    && st->fetchtime == st->curtime
	    && st->fetchdata == st->curdata)
	usleep(50000);
#endif
}

#ifdef USE_IEEEIO

void specks_ieee_server( void *vst ) {
  struct stuff *st = (struct stuff *)vst;
  struct specklist *sl;

  prctl(PR_TERMCHILD);	/* Die when parent dies */
  /* Await a request */
  for(;;) {
    time_t then;
    while(st->fetching <= 0)
	sginap(5);
    then = time(NULL);
    sl = specks_ieee_read_timestep( st, st->subsample,
				st->fetchdata, st->fetchtime );
    if(then + 5 < time(NULL))
	msg("... got %x (%d particles) from %s", sl, specks_count(sl),
		st->fname[st->fetchdata][st->fetchtime]);
    if(st->curtime == st->fetchtime && st->curdata == st->fetchdata
			&& sl != NULL) {
	st->sl = sl;
	parti_redraw();
    }
    st->fetching = 0;
  }
}

#endif /*USE_IEEEIO*/

teuben's avatar
teuben committed
void specks_set_speed( struct stuff *st, double newspeed ) {
slevy's avatar
 
slevy committed
  clock_set_speed( st->clk, newspeed );
  parti_set_speed( st, newspeed );
teuben's avatar
teuben committed
}
teuben's avatar
teuben committed

teuben's avatar
teuben committed
void specks_set_fspeed( struct stuff *st, double newspeed ) {
slevy's avatar
 
slevy committed
  /* ignore it! */
  /* or maybe use fspeed to specify finite clock resolution */
teuben's avatar
teuben committed
}

slevy's avatar
 
slevy committed
void specks_shift_ghosts( struct stuff *st ) {
  int i;
  struct specklist **slp =
	specks_timespecksptr( st, 0,
		st->nghosts > 0 ? st->nghosts-1 : 0 );
  if(slp && *slp)
    specks_discard( st, slp );
  for(i = st->nghosts; --i > 0; ) {
    slp = specks_timespecksptr( st, 0, st->nghosts );
    *slp = slp[-1];
  }
  *specks_timespecksptr( st, 0, 0 ) = NULL;
}

slevy's avatar
 
slevy committed
void specks_set_timestep( struct stuff *st )
teuben's avatar
teuben committed
{
  struct specklist *sl = st->sl;
  struct specklist **slp;
slevy's avatar
 
slevy committed
  double realtime, tmin, tmax;
  int timestep;
teuben's avatar
teuben committed

  st->used++;

slevy's avatar
 
slevy committed
  specks_timerange( st, &tmin, &tmax );
slevy's avatar
 
slevy committed
  if(st->usertrange) {
    if(tmin < st->utmin) tmin = st->utmin;
    if(tmax > st->utmax) tmax = st->utmax;
  }
  clock_set_range( st->clk, tmin, tmax, st->utwrap );
slevy's avatar
 
slevy committed
  realtime = clock_time( st->clk );
  timestep = realtime;

teuben's avatar
teuben committed
  if(st->dyndata && st->dyndatafunc) {
slevy's avatar
 
slevy committed
    struct specklist *sl;

slevy's avatar
 
slevy committed
    if(realtime == st->currealtime && st->sl != NULL && st->sl->used >= 0)
slevy's avatar
 
slevy committed
	return;
    sl = (*st->dyndatafunc)(st, realtime);
slevy's avatar
 
slevy committed
    sl->used = 1;
slevy's avatar
 
slevy committed
    st->currealtime = realtime;
teuben's avatar
teuben committed
    st->curtime = timestep;
slevy's avatar
 
slevy committed

    /* Possibly retain old snapshots -- up to st->ntimes of them! */
    /* In any case, discard previous array */
    specks_shift_ghosts( st );

teuben's avatar
teuben committed
    slp = specks_timespecksptr( st, 0, 0 );
slevy's avatar
 
slevy committed
    *slp = st->sl = sl;
slevy's avatar
 
slevy committed
    parti_set_timestep( st, realtime );
slevy's avatar
 
slevy committed
    /* Hack for kira-parti dynamic data */
    if(sl) {
	if(st->coloredby >= 0 && st->coloredby < CONSTVAL)
	    st->colorseq++;
	if(st->sizedby >= 0 && st->coloredby < CONSTVAL)
	    st->sizeseq++;
	if(st->threshvar >= 0)
	    st->threshseq++;
    }
teuben's avatar
teuben committed
    return;
  }

slevy's avatar
 
slevy committed
  if(timestep >= st->ntimes) timestep = st->ntimes - 1;
teuben's avatar
teuben committed
  if(timestep < 0) timestep = 0;

  slp = specks_timespecksptr( st, st->curdata, timestep );
  if(*slp != NULL && (*slp)->subsampled > st->subsample) {
    specks_discard( st, slp );
  }

  sl = *slp;

#ifdef USE_IEEEIO

  if(sl == NULL && (st->fetching == 0 || st->datasync)
		&& st->curdata>=0 && st->curdata<st->ndata
		&& st->datafile[st->curdata] != NULL
		&& timestep>=0 && timestep<st->ntimes
		&& st->datafile[st->curdata][timestep] != NULL) {

    st->fetchdata = st->curdata;
    st->fetchtime = timestep;

    if(getenv("NO_SPROC") || st->datasync) {
	sl = specks_ieee_read_timestep( st, st->subsample,
                                st->fetchdata, st->fetchtime );
        msg("Got %x (%d particles) from %s  (d%d t%d)\n",
		sl, specks_count(sl), st->fname[st->fetchdata][st->fetchtime],
		st->fetchdata, st->fetchtime);

    } else {
	st->fetching = 1;

	if(st->fetchpid <= 0) {
	    st->fetchpid = sproc( specks_ieee_server, PR_SADDR|PR_SFDS, st );
	    if(st->fetchpid < 0)
		perror("sproc");
	}
    }
  }

  if(st->fetchpid > 0) {
    if(kill(st->fetchpid, 0) < 0) {
	perror("specks server vanished: kill -0");
	st->fetching = 0;
	st->fetchpid = 0;
    }
  }
#endif /*USE_IEEEIO*/

  if(sl == NULL && !(st->boxtimes>timestep && st->boxes[timestep]!=NULL))
    return;
  st->sl = sl;   /* st->sl <= anima[][] */
  st->curtime = timestep;
teuben's avatar
teuben committed
  st->currealtime = timestep;
teuben's avatar
teuben committed
#if !CAVEMENU
  st->frame_time = st->curtime;	/* if non-CAVE, we have no frame-function */
#endif

  specks_set_current_annotation( st, st->annot[st->curdata][st->curtime]
		? st->annot[st->curdata][st->curtime]->text : NULL );
slevy's avatar
 
slevy committed
  parti_set_timestep( st, timestep );

teuben's avatar
teuben committed
}

void specks_set_current_annotation( struct stuff *st, char *annotation )
{
  st->annotation = annotation;
#ifdef CAVEMENU
  if(ment.annot)
    menu_settitle( ment.annot, st->annotation ? st->annotation : "" );
#endif
}

void specks_add_annotation( struct stuff *st, char *annotation, int timestep )
{
  struct specklist *sl, **slp;
  int curtime = (timestep < 0) ? st->curtime : timestep;

  if(annotation == NULL) annotation = "";

  specks_timespecksptr( st, st->curdata, curtime );
  if(curtime >= st->ntimes) curtime = 0;
  slp = &st->annot[st->curdata][curtime];
  if((sl = *slp) == NULL) {
    sl = *slp = NewN( struct specklist, 1 );
    memset(*slp, 0, sizeof(**slp));
  } else if(sl->text) {
    Free(sl->text);
  }
  sl->text = NewN( char, strlen(annotation)+1 );
  strcpy( sl->text, annotation );
}


slevy's avatar
 
slevy committed
#if !WORDS_BIGENDIAN
teuben's avatar
teuben committed
void starswap(db_star *st) {
  int i, *wp;
  /* byte-swap x,y,z, dx,dy,dz, magnitude,radius,opacity fields (32-bit float),
   * 		num (32-bit int),
   *		color (16-bit short).
   * group and type fields shouldn't need swapping,
   * assuming the compiler packs bytes into a word in increasing
   * address order.  Seems safe.
   */
  for(i = 0, wp = (int *)st; i < 10; i++)
    wp[i] = htonl(wp[i]);
  st->color = htons(st->color);
}
slevy's avatar
 
slevy committed
#endif /*!WORDS_BIGENDIAN*/
teuben's avatar
teuben committed

void specks_read_sdb( struct stuff *st, char *sdbfname, int timestep )
{
  FILE *inf = fopen(sdbfname, "r");
  long flen;
  int nspecks, i;
  float min[MAXVAL], max[MAXVAL], sum[MAXVAL];
  struct specklist *sl, **slp;
  register struct speck *sp;
  int dfltvars = (strcmp(st->sdbvars, "mcr") == 0);
  int nvars = strlen(st->sdbvars);
  int needswap = (htonl(1) != 1);

  if(inf == NULL) {
    msg("sdb: %s: cannot open: %s", sdbfname, strerror(errno));
    return;
  }
  /* Just measure file size */
  errno = 0;
  fseek(inf, 0, SEEK_END);
  flen = ftell(inf);
  if(flen == -1 || flen == 0) {
    msg("sdb: %s: can't measure length of file: %s", sdbfname, strerror(errno));

    return;
  }
  nspecks = (flen / sizeof(db_star));

  if(nspecks <= 0) {
    msg("sdb: %s: ignoring empty sdb file", sdbfname);
    return;
  }
  
  sl = NewN(struct specklist, 1);
  memset(sl, 0, sizeof(*sl));
  
  sl->bytesperspeck = SMALLSPECKSIZE( nvars );
  if(nvars > MAXVAL) nvars = MAXVAL;

  sl->scaledby = st->spacescale;
  sp = NewNSpeck(sl, nspecks);
  sl->specks = sp;

  fseek(inf, 0, SEEK_SET);
  for(i = 0; i < nspecks; i++, sp = NextSpeck(sp, sl, 1)) {
    db_star star;
    char *cp;
    int k;
    float *vp;
    if(fread(&star, sizeof(star), 1, inf) <= 0)
	break;
slevy's avatar
 
slevy committed
#if !WORDS_BIGENDIAN
teuben's avatar
teuben committed
    starswap(&star);
#endif
    sp->p.x[0] = star.x * sl->scaledby;
    sp->p.x[1] = star.y * sl->scaledby;
    sp->p.x[2] = star.z * sl->scaledby;
    if(dfltvars) {
	sp->val[0] = exp((-18-star.magnitude)*.921/*log(100)/5*/);
	sp->val[1] = star.color;
	sp->val[2] = star.radius;
    } else {
	for(vp = &sp->val[0], cp = st->sdbvars; *cp; cp++, vp++) {
	    switch(*cp) {
	    case 'm': *vp = exp((-18-star.magnitude)*.921/*log(100)/5*/); break;
	    case 'M': *vp = star.magnitude; break;
	    case 'c': *vp = star.color; break;
	    case 'r': *vp = star.radius; break;
	    case 'o': *vp = star.opacity; break;
	    case 'g': *vp = star.group; break;
	    case 't': *vp = star.type; break;
	    case 'x': *vp = star.dx; break;
	    case 'y': *vp = star.dy; break;
	    case 'z': *vp = star.dz; break;
	    case 'S': *vp = sqrt(star.dx*star.dx + star.dy*star.dy + star.dz*star.dz); break;
	    case 'n': *vp = star.num; break;
	    default: *vp = 1; break;
	    }
	}
    }

    if(i == 0) {
	for(k = 0; k < nvars; k++)
	    sum[k] = min[k] = max[k] = sp->val[k];
    } else {
	for(k = 0; k < nvars; k++) {
	    if(min[k] > sp->val[k]) min[k] = sp->val[k];
	    else if(max[k] < sp->val[k]) max[k] = sp->val[k];
	    sum[k] += sp->val[k];
	}
    }
  }
  sl->nspecks = i;
  sl->sizedby = 0;
  sl->coloredby = 1;

  /* Update statistics */
  if(sl->nspecks > 0) {
    struct valdesc *vdp = &st->vdesc[st->curdata][0];
    for(i = 0; i < nvars; i++, vdp++) {
	if(vdp->min > min[i]) vdp->min = min[i];
	if(vdp->max < max[i]) vdp->max = max[i];
	vdp->nsamples += sl->nspecks;
	vdp->sum += sum[i];
	vdp->mean = vdp->sum / vdp->nsamples;

	if(vdp->name[0] == '\0') {
	    char *name = "unk";
	    switch(st->sdbvars[i]) {
	    case 'm': name = "lumsdb"; break;
	    case 'M': name = "magsdb"; break;
	    case 'c': name = vdp->max > 16384 ? "rgb565" : "colorsdb";
		      vdp->cexact = 1;
		      break;
	    case 'r': name = "radius"; break;
	    case 'o': name = "opacity"; break;
	    case 'g': name = "group"; break;
	    case 't': name = "type"; break;
	    case 'x': name = "dx"; break;
	    case 'y': name = "dy"; break;
	    case 'z': name = "dz"; break;
	    case 'S': name = "speed"; break;
	    case 'n': name = "number"; break;
	    }
	    strcpy(vdp->name, name);
	}
    }
    specks_recolor( st, sl, st->coloredby );
    specks_resize( st, sl, st->sizedby );
  }

  /* Add to running list */
  slp = specks_timespecksptr( st, st->curdata, timestep );
  sl->next = *slp;
  *slp = sl;

  fclose(inf);
    
}

slevy's avatar
 
slevy committed
void specks_timerange( struct stuff *st, double *tminp, double *tmaxp )
teuben's avatar
teuben committed
{
  if(!st->dyndata
#if USE_KIRA
slevy's avatar
 
slevy committed
	 || !get_parti_time_range( st, tminp, tmaxp )
teuben's avatar
teuben committed
#endif
   ) {
slevy's avatar
 
slevy committed
    *tminp = 0;
slevy's avatar
 
slevy committed
    *tmaxp = st->ntimes == 0 ? 0 : st->ntimes - 1;
teuben's avatar
teuben committed
  }
teuben's avatar
teuben committed
}

int specks_get_datastep( struct stuff *st )
{
  return st->curtime;
}

teuben's avatar
teuben committed
double specks_get_realtime( struct stuff *st )
{
  return st->currealtime;
}

teuben's avatar
teuben committed
void set_interest_point( Point *p )
{
#if CAVEMENU
  char cmd[80];
  sprintf(cmd, "interest %g %g %g", p->x[0], p->x[1], p->x[2]);
  VIDI_queue_commandstr( cmd );
#else

  parti_center( p );
#endif
}

/* Only specks_timespecksptr() extends the spans of time or datasets */
struct specklist **
specks_timespecksptr( struct stuff *st, int dataset, int timestep )
{
  int d, needroom;
  struct specklist **na, **nan;
  void **ndf;
  char **nfn;

  if((timestep >= st->ntimes || dataset >= st->ndata)) {
    needroom = st->timeroom;
    if(timestep >= st->timeroom)
	needroom = 2*timestep + 15;

    for(d = 0; d < st->ndata || (dataset < MAXFILES && d <= dataset); d++) {

	if(needroom == st->timeroom && d < st->ndata)
	    continue;

	na = NewN( struct specklist *, needroom );
	nan = NewN( struct specklist *, needroom );
	ndf = NewN( void *, needroom );
	nfn = NewN( char *, needroom );
	memset(na, 0, needroom * sizeof(*na));
	memset(nan, 0, needroom * sizeof(*nan));
	memset(ndf, 0, needroom * sizeof(*ndf));
	memset(nfn, 0, needroom * sizeof(*nfn));
	if(d < st->ndata && st->anima[d])
	    memcpy( na, st->anima[d], st->ntimes * sizeof(*na) );

	if(d < st->ndata && st->annot[d])
	    memcpy( nan, st->annot[d], st->ntimes * sizeof(*nan) );

	if(d < st->ndata && st->datafile[d])
	    memcpy( ndf, st->datafile[d], st->ntimes * sizeof(*ndf) );

	if(d < st->ndata && st->fname[d])
	    memcpy( nfn, st->fname[d], st->ntimes * sizeof(*nfn) );

	/* Don't free old pointers, just in case they're in use. */
	st->anima[d] = na;
	st->annot[d] = nan;
	st->datafile[d] = ndf;
	st->fname[d] = nfn;
    }
    st->timeroom = needroom;

    if(timestep >= st->ntimes)
	st->ntimes = timestep + 1;
    if(dataset >= st->ndata && dataset < MAXFILES)
	st->ndata = dataset + 1;
  }

  return (timestep >= 0 && timestep < st->ntimes &&
			dataset >= 0 && dataset < st->ndata)
	? &st->anima[dataset][timestep] : NULL;
}

struct specklist *
specks_timespecks( struct stuff *st, int dataset, int timestep )
{
  return (timestep >= 0 && timestep < st->ntimes &&
			dataset >= 0 && dataset < st->ndata)
	? st->anima[dataset][timestep] : NULL;
}

void specks_reupdate( struct stuff *st, struct specklist *sl )
{
  struct specklist *tsl;

  if(sl != NULL && sl->threshseq != st->threshseq) {
    for(tsl = sl; tsl != NULL; tsl = tsl->next)
	specks_rethresh( st, tsl, st->threshvar );
  }

  if(sl != NULL && sl->colorseq != st->colorseq) {
    for(tsl = sl; tsl != NULL; tsl = tsl->next)
	specks_recolor( st, tsl, st->coloredby );
  }

  if(sl != NULL && sl->sizeseq != st->sizeseq) {
    for(tsl = sl; tsl != NULL; tsl = tsl->next)
	specks_resize( st, tsl, st->sizedby );
  }
}

slevy's avatar
 
slevy committed
void specks_set_time( struct stuff *st, double newtime )
teuben's avatar
teuben committed
{
  static Point lastinterest;

slevy's avatar
 
slevy committed
  clock_set_time( st->clk, newtime );
  specks_set_timestep( st );
  if(st->sl == NULL && st->ntimes > 1 && st->clk->parent == NULL) {
    /* Skip blank time-slots -- keep incrementing until either:
     *  - we find a time-slot that has (or could have) some data, or
     *  - we've run through all time-steps (avoid infinite loops!).
     */
    int nudges, ts;
    for(nudges = 0; nudges < st->ntimes; nudges++) {
	ts = (st->curtime + nudges) % st->ntimes;
	if( specks_timespecks( st, st->curdata, ts ) != NULL ||
	    (st->datafile[st->curdata] != NULL &&
		st->datafile[st->curdata][ts] != NULL) )
	    break;
    }
    clock_set_time( st->clk, ts );
    specks_set_timestep( st );
  }
  /* st->playnext = now + (st->fspeed != 0 ? 1/st->fspeed : 0); */
teuben's avatar
teuben committed

#if CAVE
slevy's avatar
 
slevy committed
  if(ment.tknob[0] == NULL) {	/* if not SC99DEMO */
slevy's avatar
 
slevy committed
    struct specklist *sl = st->sl;
slevy's avatar
 
slevy committed
    if(sl != NULL && memcmp(&sl->interest, &lastinterest, sizeof(Point))
	    && (sl->interest.x[0]!=0 || sl->interest.x[1]!=0
					    || sl->interest.x[2]!=0)) {
	char str[128];
	float scale = .004;
	lastinterest = sl->interest;
	set_interest_point( &lastinterest );
	sprintf(str, "\002setjump peak %g %g %g 0 0 0 %g",
	    lastinterest.x[0], lastinterest.x[1]-scale*5, lastinterest.x[2] + scale*5,
	    scale);
	VIDI_queue_commandstr( str );
    }
teuben's avatar
teuben committed
  }
slevy's avatar
 
slevy committed
#endif
teuben's avatar
teuben committed

slevy's avatar
 
slevy committed
  specks_reupdate( st, st->sl );
teuben's avatar
teuben committed

teuben's avatar
teuben committed
#if CAVE
  specks_refresh_menu( st );
#endif
}

static int specks_freenow( struct specklist **slp, int maxage )
{
  struct specklist *sl, **sprev;
  int any = 0;

  for(sprev = slp; (sl = *sprev) != NULL && sl->used <= maxage; ) {
    if(sl->used <= maxage) {
	*sprev = sl->freelink;
	if(sl->specks != NULL)
	    Free(sl->specks);
	Free(sl);
	any++;
    } else {
	/* too recent - might still be in use */
	sprev = &sl->freelink;
    }
  }
  return any;
}

void specks_discard( struct stuff *st, struct specklist **slp )
{
  struct specklist *sl, *slnext;

  for(sl = *slp; sl != NULL; sl = slnext) {
    slnext = sl->next;
    sl->freelink = st->scrap;
    st->scrap = sl;
  }
  *slp = NULL;
}

int specks_purge( void *vst, int nbytes, void *aarena )
{
  struct stuff *st = (struct stuff *)vst;
  int oldused = st->used;
  int oldtime = -1, oldds = -1;
  int t, ds;
  struct specklist *sl;

#ifdef sgi
  static int first = 1;
  struct mallinfo mi;
  mi = amallinfo( aarena );

  if(first) {
    first = 0;
    msg("Purging %dKbyte shmem arena (currently %dK used in %d blks, %dK free)\n",
	mi.arena>>10, mi.uordblks>>10, mi.ordblks, mi.fordblks>>10);
  }
#endif

  /* Free any known scrap first */

#define OLD_ENOUGH  4

  if(specks_freenow( &st->scrap, st->used - OLD_ENOUGH ) > 0)
    return 1;

  for(t = 0; t < st->ntimes; t++) {
    if(t == st->curtime) continue;
    for(ds = 0; ds < st->ndata; ds++) {
	sl = st->anima[ds][t];
	if(sl != NULL && sl != st->sl && sl->used < oldused) {
	    oldused = st->used;
	    oldtime = t;
	    oldds = ds;
	}
    }
  }
  if(oldtime >= 0) {
    specks_discard( st, &st->anima[oldds][oldtime] );
    specks_freenow( &st->scrap, oldtime );
    return 1;	/* We freed something, so try allocating again */
  } else {
    msg("Ran out of shmem, couldn't find anything more to purge\n");
#ifdef sgi
    msg("%dKbyte shmem arena (currently %dK used in %d blks, %dK free)\n",
	mi.arena>>10, mi.uordblks>>10, mi.ordblks, mi.fordblks>>10);

#endif
    return 0;	/* No progress made -- give up */
  }
}


/*
 * Just stash these values in our frame function so they won't change visibly
 * during a frame.  Each frame function will do this; we'll just hope that
 * they don't change as the various cave-wall processes start.
 */
void specks_current_frame( struct stuff *st, struct specklist *sl )
{
  st->frame_sl = sl;
  st->frame_time = st->curtime;
  st->frame_data = st->curdata;
  st->frame_annotation = st->annotation;
  specks_reupdate( st, sl );
}

#define	MAXXYFAN 16

extern void specks_draw_boxes( struct stuff *st, struct AMRbox *boxes, int levelmask, Matrix Ttext, int oriented );

struct cpoint {
    int rgba;
    Point p;
};

void dumpcpoints( struct cpoint *cp, int n )
{
    while(--n >= 0) {
	glColor4ubv( (GLubyte *)&cp->rgba );
	glVertex3fv( &cp->p.x[0] );
	cp++;
    }
}

slevy's avatar
 
slevy committed
static Point depth_fwd;
static float depth_d;

struct order {
  float z;
  struct speck *sp;
  struct specklist *sl;
};

static int depthcmp( const void *a, const void *b )
{
  return ((struct order *)a)->z < ((struct order *)b)->z ?    1
	: ((struct order *)a)->z > ((struct order *)b)->z ? -1 : 0;
}

static int additive_blend;

void sortedpolys( struct stuff *st, struct specklist *slhead, Matrix *Tc2wp, float radperpix, float polysize )
{
  struct speck *sp, *sbase;
  struct order *op, *obase;
  int i, k, total, skip;
  struct specklist *sl;
  int usethresh = st->usethresh&P_USETHRESH ? THRESHBIT : 0;
  int bps = 0;
  int prevrgba = -1;
  int usearea = st->polyarea;
  int sizevar = st->polysizevar;
  int polyorivar = st->polyorivar0;
  int texturevar = st->texturevar;
  int txno;
  int texturing = -1;
  float s;
  float polyminrad = st->polymin * radperpix;
  float polymaxrad = st->polymax * radperpix;
  float mins2d = polyminrad * polyminrad;
  int rgba;
  int alpha = st->alpha * 255;
  int nfan = st->npolygon<MAXXYFAN ? st->npolygon : MAXXYFAN;
  float xyfan[MAXXYFAN][2];
  Point sfan[MAXXYFAN], pfan[MAXXYFAN];
  Matrix Tc2w = *Tc2wp;
  float scl = vlength( (Point *)&Tc2w.m[0*4+0] );
  Texture *wanttx;
  int additive = additive_blend;
  int wantblend = additive;

  int useclip = (st->clipbox.level > 0);
  Point clipp0 = st->clipbox.p0;
  Point clipp1 = st->clipbox.p1;

  for(total = 0, sl = slhead; sl != NULL; sl = sl->next) {
    if(sl->text != NULL || sl->nspecks == 0 || sl->special != SPECKS)
	continue;
    skip = st->subsample;
    if(sl->subsampled != 0)	/* if already subsampled */
	skip /= sl->subsampled;
    if(skip <= 0) skip = 1;

    if(bps < sl->bytesperspeck) bps = sl->bytesperspeck;
    if(usethresh) {
	for(i=sl->nspecks, sp=sl->specks; i>0; i-=skip, sp=NextSpeck(sp,sl,skip)) {
	    if((usethresh & sp->rgba) == 0)
		total++;
	}
    } else {
	total += sl->nspecks / skip;
    }
  }

  obase = op = (struct order *)malloc( (total+1) * sizeof(struct order) );
  for(sl = slhead; sl != NULL; sl = sl->next) {
    if(sl->text != NULL || sl->nspecks == 0 || sl->special != SPECKS)
	continue;
    skip = st->subsample;
    if(sl->subsampled != 0)	/* if already subsampled */
	skip /= sl->subsampled;
    if(skip <= 0) skip = 1;
    for(i=sl->nspecks, sp=sl->specks; i > 0; i-=skip, sp=NextSpeck(sp,sl,skip)) {
	float dist;
	if(usethresh & sp->rgba)
	    continue;
	dist = VDOT( &sp->p, &depth_fwd ) + depth_d;
	if(dist < 0)
	    continue;
	if(useclip &&
	  (sp->p.x[0] < clipp0.x[0] ||
	   sp->p.x[0] > clipp1.x[0] ||
	   sp->p.x[1] < clipp0.x[1] ||
	   sp->p.x[1] > clipp1.x[1] ||
	   sp->p.x[2] < clipp0.x[2] ||
	   sp->p.x[2] > clipp1.x[2]))
	    continue;
	op->z = dist;
	op->sp = sp;
	op->sl = sl;
	op++;
    }
  }

  total = op - obase;
  qsort( obase, total, sizeof(*obase), depthcmp );

  prevrgba = 0;

  /* Build prototype fan -- unit disk in screen plane */
  for(i = 0; i < nfan; i++) {
    float theta = 2*M_PI*i/nfan;
    xyfan[i][0] = cos(theta);
    xyfan[i][1] = sin(theta);
    vcomb( &sfan[i], xyfan[i][0] / scl, (Point *)&Tc2w.m[0*4+0],
		     xyfan[i][1] / scl, (Point *)&Tc2w.m[1*4+0] );
  }

  if(st->usetextures == 0 || SMALLSPECKSIZE(texturevar) > bps)
    texturevar = -1;
  if(SMALLSPECKSIZE(polyorivar) > bps)
    polyorivar = -1;

  for(i = 0, op = obase; i < total; i++, op++) {
    float dist, size;

    sp = op->sp;
    dist = op->z;
    size = sp->val[sizevar] * polysize;
    if(usearea) {
	if(size < dist * dist * mins2d)
	    continue;
	size = sqrtf(size);
    } else {
	if(size < dist * polyminrad)
	    continue;
    } 
    if(size > dist * polymaxrad)
	size = dist * polymaxrad;

    rgba = sp->rgba & ~THRESHBIT;
    if(rgba != prevrgba) {
	prevrgba = rgba;
	rgba = RGBALPHA( prevrgba, alpha );
	glColor4ubv( (GLubyte *)&rgba );
    }

    if(texturevar >= 0 &&
	    (txno = sp->val[texturevar]) >= 0 &&
	    txno < st->ntextures &&
	    (wanttx = st->textures[txno]) != NULL) {

	txbind( wanttx, &texturing );
	wantblend = (wanttx->flags & TXF_ADD) ? 1 : additive_blend;
    } else if(texturing) {
	glDisable( GL_TEXTURE_2D );
	texturing = 0;
    }

    if(wantblend != additive) {
	additive = wantblend;
	glBlendFunc( GL_SRC_ALPHA, additive ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA );
    }

    if(polyorivar >= 0 && sp->val[polyorivar] < 9) {
	float *xv = &sp->val[polyorivar];
	float *yv = &sp->val[polyorivar+3];

	glBegin( GL_TRIANGLE_FAN );
	if(texturing) {
	    for(k = 0; k < nfan; k++) {
		glTexCoord2fv( &xyfan[k][0] );
		glVertex3f(
		    sp->p.x[0] + size*(xyfan[k][0]*xv[0] + xyfan[k][1]*yv[0]),
		    sp->p.x[1] + size*(xyfan[k][0]*xv[1] + xyfan[k][1]*yv[1]),
		    sp->p.x[2] + size*(xyfan[k][0]*xv[2] + xyfan[k][1]*yv[2]));
	    }
	} else {
	    for(k = 0; k < nfan; k++) {
		glVertex3f(
		    sp->p.x[0] + size*(xyfan[k][0]*xv[0] + xyfan[k][1]*yv[0]),
		    sp->p.x[1] + size*(xyfan[k][0]*xv[1] + xyfan[k][1]*yv[1]),
		    sp->p.x[2] + size*(xyfan[k][0]*xv[2] + xyfan[k][1]*yv[2]));
	    }
	}
	glEnd();

    } else {
	glBegin( GL_TRIANGLE_FAN );
	if(texturing) {
	    for(k = 0; k < nfan; k++) {
		glTexCoord2fv( &xyfan[k][0] );
		glVertex3f(
		    sp->p.x[0] + size*sfan[k].x[0],
		    sp->p.x[1] + size*sfan[k].x[1],
		    sp->p.x[2] + size*sfan[k].x[2] );
	    }
	} else {
	    for(k = 0; k < nfan; k++) {
		glVertex3f(
		    sp->p.x[0] + size*sfan[k].x[0],
		    sp->p.x[1] + size*sfan[k].x[1],
		    sp->p.x[2] + size*sfan[k].x[2] );
	    }
	}
	glEnd();
    }
  }
  free(obase);
  if(texturing > 0)
    txbind( NULL, NULL );
}
  
  

teuben's avatar
teuben committed
void drawspecks( struct stuff *st )
{
  int i, slno, k;
  int rgba, alpha, prevrgba = 0;
  float prevsize = 0;
  struct specklist *sl, *slhead;
  register struct speck *p;
  Matrix Tw2c, Tc2w, Ttext, Tproj, Ttemp;
  static Point zero = {0,0,0};
  int xywh[4];
  float radperpix;
  Point tp, fan[MAXXYFAN];
  Point eyepoint;
  Point fwd;
  float fwdd;
  float tscale, scl;
  int skip;
  static int nxyfan = 0;
  static float xyfan[MAXXYFAN][2];
  static unsigned char randskip[256];
  int randix = 0;
  int fast = st->fast;
  int inpick = st->inpick;
  int usethresh = st->usethresh&P_USETHRESH ? THRESHBIT : 0;
  int fixeddist;
  float polyminrad, polymaxrad;
  float threshmin = st->thresh[0];
  float threshmax = st->thresh[1];
  int useclip = (st->clipbox.level > 0);
  Point clipp0 = st->clipbox.p0;
  Point clipp1 = st->clipbox.p1;

  float plum = st->psize;

  float knee1dist2 = st->fadeknee1 * st->fadeknee1;
  float knee2dist2 = st->fadeknee2 * st->fadeknee2;
  float orthodist2 = st->fadeknee2 * st->fadeknee2;
  float steep2knee2 = st->knee2steep * st->knee2steep / knee2dist2;
  float faderball2 = 1 / (st->fadeknee2 * st->fadeknee2);
  Point fadecen = st->fadecen;
  int fademodel = st->fade;

  if(!st->useme)
    return;

  switch(fademodel) {
  case F_CONSTANT:
	if(orthodist2 <= 0)
	    orthodist2 = 1;
	break;
  case F_KNEE12:
	if(st->fadeknee1 >= st->fadeknee2 || st->fadeknee1 <= 0)
	    fademodel = F_KNEE2;	/* and fall into... */
  case F_KNEE2:
	if(st->fadeknee2 <= 0)
	    fademodel = F_SPHERICAL;
	break;
  case F_LREGION:
	if(st->fadeknee2 <= 0) faderball2 = 1;
	break;
  }


  { float r=0,g=0,b=0;	/* Ugh. Allow background to be non-black */
slevy's avatar
 
slevy committed
    sscanf(parti_bgcolor(NULL), "%f%f%f", &r,&g,&b);
    additive_blend = (r+g+b == 0);
teuben's avatar
teuben committed
  }

  if(st->clipbox.level != 0) {
    GLdouble plane[4];

    if(st->clipbox.level == 1) {
	struct AMRbox b[2];
	b[0] = st->clipbox;
	b[0].level = 0;
	b[1].level = -1;
	specks_draw_boxes( st, b, ~0, Tidentity, 1 );
    }
    for(i = 0; i < 3; i++) {
	plane[0] = plane[1] = plane[2] = 0;
	plane[i] = 1;
	plane[3] = -st->clipbox.p0.x[i];
	glClipPlane( GL_CLIP_PLANE0 + i, plane );
	glEnable( GL_CLIP_PLANE0 + i );
	plane[i] = -1;
	plane[3] = st->clipbox.p1.x[i];
	glClipPlane( GL_CLIP_PLANE0 + 3 + i, plane );
	glEnable( GL_CLIP_PLANE0 + 3 + i );
    }
  }


  if(nxyfan != st->npolygon && st->npolygon > 0) {
    if(st->npolygon > MAXXYFAN) st->npolygon = MAXXYFAN;
    nxyfan = st->npolygon;
    for(i = 0; i < nxyfan; i++) {
	float th = i*2*M_PI / nxyfan;
	xyfan[i][0] = cos(th);
	xyfan[i][1] = sin(th);
    }
    srandom(11);
    for(i = 0; i < 256; i++)
	randskip[i] = random() & 0xFF;
  }


slevy's avatar
 
slevy committed
  alpha = st->alpha * 255;
slevy's avatar
 
slevy committed
  rgba = RGBALPHA( RGBWHITE, alpha );	/* BIG-ENDIAN (1,1,1,alpha) */
teuben's avatar
teuben committed

  /* Find displacements which lie in the screen plane, for making
   * billboard-style polygonal patches, and for text.
   */
  glGetFloatv( GL_MODELVIEW_MATRIX, Tw2c.m );
  eucinv( &Tc2w, &Tw2c );
  scl = vlength( (Point *)&Tw2c.m[0] );
  for(i = 0; i < nxyfan; i++) {
    tp.x[0] = scl*st->polysize*xyfan[i][0];
    tp.x[1] = scl*st->polysize*xyfan[i][1];
    tp.x[2] = 0;
    vtfmvector( &fan[i], &tp, &Tc2w );
  }

  /* Find projection matrix and screen (well, viewport) size,
   * so we can convert angular sizes to screen (pixel) sizes,
   * in radians per pixel.
   */
  glGetFloatv( GL_PROJECTION_MATRIX, Tproj.m );
  glGetIntegerv( GL_VIEWPORT, xywh );
  radperpix = 1 / (.5*xywh[2] * Tproj.m[0*4+0]);

  /* Construct a "forward" vector in object coords too, for measuring
   * distance from camera plane.  Note camera looks toward its -Z axis not +Z!
   */
  tp.x[0] = 0, tp.x[1] = 0, tp.x[2] = -1;
  vtfmvector( &fwd, &tp, &Tc2w );
  vunit( &fwd, &fwd );
  /*
   * Actually we want a plane equation, whose value is zero in the
   * eye plane.  Camera-space distance from camera plane = 
   *		vdot( &objectpoint, &fwd ) + fwdd.
   */
  vtfmpoint( &eyepoint, &zero, &Tc2w );
  fwdd = -vdot( &eyepoint, &fwd );

  tscale = scl * st->textsize;
  mcopy( &Ttemp, &Tidentity );
  Ttemp.m[0*4+0] = Ttemp.m[1*4+1] = Ttemp.m[2*4+2] = tscale;
  mmmul( &Ttext, &Ttemp, &Tc2w );
  vsettranslation( &Ttext, &zero );

  glDisable( GL_LIGHTING );

  /* Draw any boxes (even if we have no specks) for this timestep */
  if(st->useboxes && st->boxlevelmask != 0
	&& st->frame_time >= 0 && st->frame_time < st->boxtimes
	&& st->boxes[st->frame_time] != NULL) {
    specks_draw_boxes( st, st->boxes[st->frame_time], st->boxlevelmask, Ttext, 0 );
  }
  if(st->useboxes && st->staticboxes != NULL) {
    specks_draw_boxes( st, st->staticboxes, st->boxlevelmask, Ttext, st->boxaxes );
  }

  slhead = st->frame_sl;	/* st->sl as snapped by specks_ffn() */
  if(slhead == NULL)
    slhead = st->sl;		/* maybe there is no specks_ffn() */
  if(slhead == NULL)
    return;

slevy's avatar
 
slevy committed
#if USE_KIRA
  kira_draw( st, slhead, &Tc2w, radperpix );
#endif
teuben's avatar
teuben committed

  skip = st->subsample;
  if(slhead->subsampled != 0)	/* if already subsampled */
    skip /= slhead->subsampled;
  if(skip == 0) skip = 1;

  for(sl = slhead; sl != NULL; sl = sl->next)
    sl->used = st->used;

  if((unsigned int)st->sizedby <= MAXVAL
			&& (unsigned int)st->curdata < MAXFILES
			&& st->vdesc[st->curdata][st->sizedby].lum != 0) {
	plum *= st->vdesc[st->curdata][st->sizedby].lum;
  }
slevy's avatar
 
slevy committed
  if(st->subsample > 0 && st->everycomp)
teuben's avatar
teuben committed
      plum *= st->subsample;	/* Compensate for "every" subsampling */


  if(st->alpha >= 1) {
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
  } else {
    glEnable(GL_BLEND);
    glBlendFunc( GL_SRC_ALPHA, additive_blend ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA );
    glEnable(GL_DEPTH_TEST);
    glDepthMask( GL_FALSE );
  }

  if(inpick) glLoadName(0);
  
slevy's avatar
 
slevy committed

teuben's avatar
teuben committed
  if(st->usepoly && st->polysize > 0) {
    int texturing = 0;
    int texturevar = st->usetextures && st->texturevar >= 0
			&& st->texturevar < MAXVAL
		   ? st->texturevar : -1;
    int usearea = st->polyarea;
    int sizevar = st->polysizevar;
    float polysize = st->polysize;
    float mins2d;

    int txno;

    glMatrixMode( GL_TEXTURE );
    glLoadIdentity();
    glTranslatef( .5, .5, 0 );
    glScalef( st->txscale, st->txscale, st->txscale );
    glMatrixMode( GL_MODELVIEW );


    polyminrad = st->polymin * radperpix;
    polymaxrad = st->polymax * radperpix;
    mins2d = polyminrad*polyminrad;

    if(sizevar == -1) {
	/* If polygon size is tied to point size,
	 * then include pointsize scale factors in polygon scaling.
	 */
      if((unsigned int)st->sizedby <= MAXVAL
		&& (unsigned int)st->curdata < MAXFILES
		&& st->vdesc[st->curdata][st->sizedby].lum != 0)
	polysize *= st->vdesc[st->curdata][st->sizedby].lum;
slevy's avatar
 
slevy committed
      if(st->subsample > 0 && st->everycomp)
teuben's avatar
teuben committed
        polysize *= st->subsample; /* Compensate for "every" subsampling */
    }

slevy's avatar
 
slevy committed
    if(st->depthsort && !inpick) {
	depth_fwd = fwd;
	depth_d = fwdd;
	sortedpolys( st, slhead, &Tc2w, radperpix, polysize );

    } else {
      for(sl = slhead, slno = 1; sl != NULL; sl = sl->next, slno++) {
slevy's avatar
 
slevy committed
	if(sl->text != NULL || sl->special != SPECKS) continue;
teuben's avatar
teuben committed
	if(inpick) {
	    glLoadName(slno);
	    glPushName(0);
	}
	for(i = 0, p = sl->specks; i < sl->nspecks; i+=skip, p = NextSpeck( p, sl, skip )) {
	    float dist = VDOT( &p->p, &fwd ) + fwdd;
	    float size;

	    if(dist <= 0) continue;

	    if(usethresh & p->rgba)
		continue;

	    size = p->val[sizevar] * polysize;
	    if(usearea) {
		if(size < dist * dist * mins2d)
		    continue;
		size = sqrtf(size);
	    } else {
		if(size < dist * polyminrad)
		    continue;
	    } 
	    if(size > dist * polymaxrad)
		size = dist * polymaxrad;

	    rgba = p->rgba & ~THRESHBIT;
	    if(rgba != prevrgba) {
		prevrgba = rgba;
		rgba = RGBALPHA( prevrgba, alpha );
		glColor4ubv( (GLubyte *)&rgba );
	    }
	    if(st->polyorivar0 >= 0 && p->val[st->polyorivar0] < 9) {
		for(k = 0; k < nxyfan; k++) {
		    vcomb( &fan[k],
			size*xyfan[k][0], (Point *)&p->val[st->polyorivar0],
			size*xyfan[k][1], (Point *)&p->val[st->polyorivar0+3] );
		}
	    } else if(p->size != prevsize) {
		float s = scl*size;
		for(k = 0; k < nxyfan; k++) {
slevy's avatar
 
slevy committed
		    vcomb( &fan[k], s*xyfan[k][0], (Point *)&Tc2w.m[0*4+0],
				    s*xyfan[k][1], (Point *)&Tc2w.m[1*4+0] );
teuben's avatar
teuben committed
		}
		prevsize = size;
	    }

#define PFAN(vno, comp)  p->p.x[comp] + fan[vno].x[comp]

	    if(inpick) {
		glLoadName( i );
		glBegin( GL_TRIANGLE_FAN );
		for(k = 0; k < nxyfan; k++) {
		    glVertex3f( PFAN(k,0), PFAN(k,1), PFAN(k,2) );
		}
		glEnd();

	    } else if(texturevar >= 0
slevy's avatar
 
slevy committed
		    && (txno = p->val[texturevar]) >= 0
teuben's avatar
teuben committed
		    && txno < st->ntextures &&
		    st->textures[txno] != NULL) {

		txbind( st->textures[txno], &texturing );

		glBegin( GL_TRIANGLE_FAN );
		for(k = 0; k < nxyfan; k++) {
		    glTexCoord2fv( &xyfan[k][0] );
		    glVertex3f( PFAN(k,0), PFAN(k,1), PFAN(k,2) );
		}
		glEnd();

	    } else {
		if(texturing) {
		    texturing = 0;
		    glDisable( GL_TEXTURE_2D );
		}
		glBegin(GL_TRIANGLE_FAN);
		for(k = 0; k < nxyfan; k++)
		    glVertex3f( PFAN(k,0), PFAN(k,1), PFAN(k,2) );
		glEnd();
	    }
#undef PFAN

	}
	if(inpick) glPopName();
slevy's avatar
 
slevy committed
      }
teuben's avatar
teuben committed
    }
    if(texturing) {
slevy's avatar
 
slevy committed
	txbind( NULL, NULL );
teuben's avatar
teuben committed
	glDisable( GL_TEXTURE_2D );
    }
    glMatrixMode( GL_TEXTURE );
    glLoadIdentity();
    glMatrixMode( GL_MODELVIEW );
  }

  if(st->usepoint && !(st->useboxes == 2)) {

#define MAXPTSIZE 16	/* in half-point units */
#define PERBUCKET 64	/* max points per bucket */

    struct cpoint sized[MAXPTSIZE*2][PERBUCKET];
    int nsized[MAXPTSIZE*2];
    unsigned char invgamma[256];
    float invgam = (st->gamma <= 0) ? 0 : 1/st->gamma;

    for(i = 0; i < 256; i++)
	invgamma[i] = (int) (255.99 * pow( i/255., invgam ));

    if(inpick) {
	for(sl = slhead, slno = 1; sl != NULL; sl = sl->next, slno++) {
slevy's avatar
 
slevy committed
	    if(sl->text != NULL || sl->special != SPECKS) continue;
teuben's avatar
teuben committed
	    glLoadName(slno);
	    glPushName(0);
	    for(i = 0, p = sl->specks; i < sl->nspecks; i+=skip, p = NextSpeck(p, sl, skip)) {
		if(usethresh & p->rgba)
		    continue;

		glLoadName(i);
		glBegin(GL_POINTS);
		glVertex3fv( &p->p.x[0] );
		glEnd();
	    }
	    glPopName();
	}

    } else if(fast) {
	static unsigned char apxsize[MAXPTSIZE*MAXPTSIZE];
	unsigned char faintrand[256];
	int pxsize, oldpxsize;
	int pxmin, pxmax;

	pxmin = 256 * st->pfaint;
	if(st->plarge > MAXPTSIZE) st->plarge = MAXPTSIZE;
	pxmax = 256 * st->plarge * st->plarge;

	if(apxsize[1] == 0) {
	    for(i=0; i<COUNT(apxsize); i++)
		apxsize[i] = (int)ceil(sqrtf(i+1));
	}
	for(i = 0; i < 256; i++)
	    faintrand[i] = randskip[i] * st->pfaint;

	/* Render using fast (non-antialiased) points */
	glDisable( GL_POINT_SMOOTH );
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, additive_blend ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA );

	prevrgba = 0;
	prevsize = 0;

	sl = slhead;

	pxsize = oldpxsize = 1;
	glPointSize( pxsize );
	glColor4ubv( (GLubyte *)&rgba );

	for(i = 0; i < MAXPTSIZE*2; i++)
	    nsized[i] = 0;

	glBegin( GL_POINTS );
	for(sl = slhead; sl != NULL; sl = sl->next) {
slevy's avatar
 
slevy committed
	    if(sl->text != NULL || sl->special != SPECKS) continue;
teuben's avatar
teuben committed
	    for(i = 0, p = sl->specks; i < sl->nspecks; i+=skip, p= NextSpeck(p, sl, skip)) {
		int lum, myalpha;
		float dist = VDOT( &p->p, &fwd ) + fwdd;
		if(dist <= 0)	/* Behind eye plane */
		    continue;

		if(usethresh & p->rgba)
		    continue;

		if(useclip &&
		  (p->p.x[0] < clipp0.x[0] ||
		   p->p.x[0] > clipp1.x[0] ||
		   p->p.x[1] < clipp0.x[1] ||
		   p->p.x[1] > clipp1.x[1] ||
		   p->p.x[2] < clipp0.x[2] ||
		   p->p.x[2] > clipp1.x[2]))
		    continue;


		lum = 256 * plum * p->size / (dist*dist);

		if(lum < pxmin) {
		    if(lum <= faintrand[(i*i+i) /*randix++*/ & 0xFF])
			continue;
		    pxsize = 1;
		    myalpha = pxmin;
		} else if(lum < 256) {
		    pxsize = 1;
		    myalpha = lum;
		} else if(lum < pxmax) {
		    pxsize = apxsize[lum>>8];
		    myalpha = lum / (pxsize*pxsize);
		} else {
		    /* Could use a polygon here, instead. */
		    pxsize = st->plarge;
		    myalpha = 255;
		}

		if(pxsize < 1 || pxsize > 6 || myalpha <= 0 || myalpha > 255) {
		    static int oops;
		    oops++;
		}
		if(myalpha < 0 || myalpha > 255 || invgamma[myalpha] <= 0 || invgamma[myalpha] > 255) {
		    static int oops2;
		    oops2++;
		}
		rgba = RGBALPHA( p->rgba&~THRESHBIT, invgamma[myalpha] & 0xFC );

		if(pxsize != oldpxsize) {
		    if(nsized[pxsize] >= PERBUCKET) {
			glEnd();
			glPointSize(pxsize);
			glBegin( GL_POINTS );
			dumpcpoints( &sized[pxsize][0], PERBUCKET );
			nsized[pxsize] = 0;
			oldpxsize = pxsize;
			glColor4ubv( (GLubyte *)&rgba );
			prevrgba = rgba;
			glVertex3fv( &p->p.x[0] );

		    } else {
			struct cpoint *cp = &sized[pxsize][nsized[pxsize]];
			cp->rgba = rgba;
			cp->p = p->p;
			nsized[pxsize]++;
		    }
		} else {
		    /* same as current pointsize */
		    if(rgba != prevrgba) {
			glColor4ubv( (GLubyte *)&rgba );
			prevrgba = rgba;
		    }

		    glVertex3fv( &p->p.x[0] );
		}
	    }
	}
	glEnd();

	for(i = 0; i < MAXPTSIZE; i++) {
	    if(nsized[i] > 0) {
		glPointSize( i );
		glBegin( GL_POINTS );
		dumpcpoints( &sized[i][0], nsized[i] );
		glEnd();
	    }
	}

    } else {

	/* 
	 * Render using anti-aliased points
	 */
	static unsigned char apxsize[(MAXPTSIZE*2)*(MAXPTSIZE*2)];
	unsigned char faintrand[256];
	static float percoverage[MAXPTSIZE*2];
	static int needMesaHack = 0;
	int pxsize, oldpxsize;
	int pxmin, pxmax;
	int minsize, minalpha;

	pxmin = (256 * (2*2)) * st->pfaint;
	if(st->plarge > MAXPTSIZE*2) st->plarge = MAXPTSIZE*2;
	pxmax = (256 * (2*2)) * st->plarge * st->plarge;

	if(apxsize[1] == 0) {
	    char *version = (char *)glGetString( GL_VERSION );

	    /* Hack: MESA doesn't handle small anti-aliased points well;
	     * force them all to be of size 1.0 or 2.0 (i.e. pxsize == 2 or 4),
	     * and non-antialiased.
	     */
	    if(getenv("MESAHACK") != NULL)
		needMesaHack = atoi(getenv("MESAHACK"));
	    else if(version &&
		  (!strncmp(version, "CRIME", 5) || !strncmp(version, "IR", 2)))
		needMesaHack = 0;	/* O2 & IR: nice anti-aliased points */
	    else
		needMesaHack = -1;	/* IMPACT (& others?): ugly AA points */

	    for(i=0; i<COUNT(apxsize); i++) {
		apxsize[i] = (int)ceil(sqrtf(i+1));
		if(needMesaHack>0) {
		    if(apxsize[i] <= 2) apxsize[i] = 2;
		    else if(needMesaHack<2 && apxsize[i] <= 4) apxsize[i] = 4;
		}
	    }

	    for(i = 1; i < MAXPTSIZE*2; i++) {
		percoverage[i] = 1.0 / (i*i);
		if(needMesaHack>0) {
		    if(i <= 2) percoverage[i] = .25;
		    else if(needMesaHack<2 && i <= 4) percoverage[i] = .0625;
		}
	    }
	    percoverage[0] = 1.0;

	    if(needMesaHack < 0)
		percoverage[1] = 0.25;	/* if gfx treats psize 0.5 == 1.0 */

	}

	minsize = apxsize[ pxmin >> 8 ];
	minalpha = pxmin * percoverage[ minsize ];

	for(i = 0; i < 256; i++)
	    faintrand[i] = randskip[i] * st->pfaint;

	/* Render using antialiased points */
	glEnable( GL_POINT_SMOOTH );
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, additive_blend ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA );

	prevrgba = 0;
	prevsize = 0;

	sl = slhead;

	pxsize = 2;
	oldpxsize = 99;
	glPointSize( pxsize );
	glColor4ubv( (GLubyte *)&rgba );

	for(i = 0; i < MAXPTSIZE*2; i++)
	    nsized[i] = 0;

	glBegin( GL_POINTS );
	for(sl = slhead; sl != NULL; sl = sl->next) {
slevy's avatar
 
slevy committed
	    if(sl->text != NULL || sl->special != SPECKS) continue;
slevy's avatar
 
slevy committed
	    for(i = 0, p = sl->specks; i < sl->nspecks; i+=skip, p=NextSpeck(p,sl,skip)) {
teuben's avatar
teuben committed
		int lum, myalpha;
		float dist, dist2, dx, dy, dz;

		if(usethresh & p->rgba)
		    continue;
		if(useclip &&
		  (p->p.x[0] < clipp0.x[0] ||
		   p->p.x[0] > clipp1.x[0] ||
		   p->p.x[1] < clipp0.x[1] ||
		   p->p.x[1] > clipp1.x[1] ||
		   p->p.x[2] < clipp0.x[2] ||
		   p->p.x[2] > clipp1.x[2]))
		    continue;


		switch(fademodel) {
		case F_PLANAR:
		    dist = VDOT( &p->p, &fwd ) + fwdd;
		    if(dist <= 0)	/* Behind eye plane */
			continue;
		    dist2 = dist*dist;
		    break;
		case F_CONSTANT:
		    dist2 = orthodist2;
		    break;
		case F_SPHERICAL:
		    dx = p->p.x[0]-eyepoint.x[0];
		    dy = p->p.x[1]-eyepoint.x[1];
		    dz = p->p.x[2]-eyepoint.x[2];
		    dist2 = dx*dx + dy*dy + dz*dz;
		    break;

		case F_LREGION:	/* not impl yet */
		    dist = VDOT( &p->p, &fwd ) + fwdd;
		    if(dist <= 0)	/* Behind eye plane */
			continue;
		    dx = p->p.x[0] - fadecen.x[0];
		    dy = p->p.x[1] - fadecen.x[1];
		    dz = p->p.x[2] - fadecen.x[2];
		    dist2 = dist * st->fadeknee2
				/ (1 + (dx*dx + dy*dy + dz*dz) * faderball2);
		    break;
		case F_LINEAR:
		    dist = VDOT( &p->p, &fwd ) + fwdd;
		    if(dist <= 0)	/* Behind eye plane */
			continue;
		    dist2 = dist * st->fadeknee2;
		    break;
			
		case F_KNEE2:
		    dx = p->p.x[0]-eyepoint.x[0];
		    dy = p->p.x[1]-eyepoint.x[1];
		    dz = p->p.x[2]-eyepoint.x[2];
		    dist2 = dx*dx + dy*dy + dz*dz;
		    if(dist2 > knee2dist2)
			dist2 *= 1 + steep2knee2 * (dist2 - knee2dist2);
		    break;
		case F_KNEE12:
		    dx = p->p.x[0]-eyepoint.x[0];
		    dy = p->p.x[1]-eyepoint.x[1];
		    dz = p->p.x[2]-eyepoint.x[2];
		    dist2 = dx*dx + dy*dy + dz*dz;
		    if(dist2 < knee1dist2)
			dist2 = knee1dist2;
		    else if(dist2 > knee2dist2)
			dist2 *= 1 + steep2knee2 * (dist2 - knee2dist2);
		    break;
		}

		lum = (256 * (2*2)) * plum * p->size / dist2;

		if(lum <= pxmin) {
		    if(lum <= faintrand[(i*i+i) /*randix++*/ & 0xFF])
			continue;
		    pxsize = minsize;
		    myalpha = minalpha;
		} else if(lum < pxmax) {
		    pxsize = apxsize[lum>>8];
		    myalpha = lum * percoverage[pxsize];
		} else {
		    /* Could use a polygon here, instead. */
		    pxsize = 2 * st->plarge;
		    myalpha = 255;
		}

		if(pxsize < 1 || pxsize >= MAXPTSIZE*2 || myalpha <= 0 || myalpha > 255) {
		    static int oops;
		    oops++;
		    pxsize = MAXPTSIZE*2 - 1;
		    myalpha = 255;
		}
		rgba = RGBALPHA( p->rgba&~THRESHBIT, invgamma[myalpha] & 0xFC );

		if(pxsize != oldpxsize) {
		    if(nsized[pxsize] >= PERBUCKET) {
			glEnd();
			if(needMesaHack>0 && (pxsize <= 4) != (oldpxsize <= 4)) {
			    if(pxsize <= 4) glDisable( GL_POINT_SMOOTH );
			    else glEnable( GL_POINT_SMOOTH );
			}
			glPointSize(.5*pxsize);
			glBegin( GL_POINTS );
			dumpcpoints( &sized[pxsize][0], PERBUCKET );
			nsized[pxsize] = 0;
			oldpxsize = pxsize;
			glColor4ubv( (GLubyte *)&rgba );
			prevrgba = rgba;
			glVertex3fv( &p->p.x[0] );

		    } else {
			struct cpoint *cp = &sized[pxsize][nsized[pxsize]];
			cp->rgba = rgba;
			cp->p = p->p;
			nsized[pxsize]++;
		    }
		} else {
		    /* same as current pointsize */
		    if(rgba != prevrgba) {
			glColor4ubv( (GLubyte *)&rgba );
			prevrgba = rgba;
		    }

		    glVertex3fv( &p->p.x[0] );
		}
	    }
	}
	glEnd();

	if(needMesaHack>0)
	    glDisable( GL_POINT_SMOOTH );

	for(i = 0; i < MAXPTSIZE*2; i++) {
	    if(needMesaHack>0 && i == 4)
		glEnable( GL_POINT_SMOOTH );
	    if(nsized[i] > 0) {
		glPointSize( .5*i );
		glBegin( GL_POINTS );
		dumpcpoints( &sized[i][0], nsized[i] );
		glEnd();
	    }
	}

    }
  }

  if(st->usetext && st->textsize != 0) {
    for(sl = slhead, slno = 1; sl != NULL; sl = sl->next, slno++) {
	if(sl->text != NULL && (p = sl->specks) != NULL) {
	    float dist = VDOT( &p->p, &fwd ) + fwdd;
	    float tsize;

	    if(dist <= 0)
		continue;

	    if(inpick) glLoadName(slno);
teuben's avatar
teuben committed

	    tsize = st->textsize * p->size;
	    if(tsize < dist * (radperpix * st->textmin)) {
		/* Could draw a stub here -- a simple line segment of
		 * about the right length
		 */
		Point ep;
		/* extract top row of Ttext -- this is the unit screen-space X
		 * vector, expressed in world coords.  Scale to suit.
		 */
		vcomb( &ep,
		    p->size * sfStrWidth(sl->text), (Point *)&Ttext.m[0],
		    1, &p->p );
		rgba = RGBALPHA( p->rgba&~THRESHBIT, alpha );
		glColor4ubv( (GLubyte *)&rgba );
		glBegin( GL_LINES );
		glVertex3fv( p->p.x );
		glVertex3fv( ep.x );
		glEnd();
		continue;
	    }

	    /* Otherwise, it's big enough to see -- draw actual text */

	    glPushMatrix();
	    glTranslatef( p->p.x[0], p->p.x[1], p->p.x[2] );
	    if(st->usetextaxes) {
		static unsigned char col[3][3] = {255,0,0, 0,255,0, 0,0,255};
		static float pos[3][3] = {1,0,0, 0,1,0, 0,0,1};
		glPushMatrix();
		glScalef( tsize, tsize, tsize );
		glBegin(GL_LINES);
		for(k = 0; k < 3; k++) {
		    glColor3ubv( &col[k][0] );
		    glVertex3f(0,0,0);
		    glVertex3fv( &pos[k][0] );
		}
		glEnd();
		glPopMatrix();
	    }
	    rgba = RGBALPHA( p->rgba&~THRESHBIT, alpha );
	    glColor4ubv( (GLubyte *)&rgba );
	    glMultMatrixf( Ttext.m );
	    sfStrDraw( sl->text, p->size, NULL );
	    glPopMatrix();
	}
    }
  }

  if(inpick) glLoadName(0);

  for(i = 0; i < 6; i++)	/* in case clipbox was enabled when we began */
    glDisable( GL_CLIP_PLANE0 + i );


  glColor4ub(255,255,255,255);
  glDisable( GL_BLEND );
  glEnable( GL_DEPTH_TEST );	/* was already enabled */
  glDepthMask( GL_TRUE );
}

void specks_draw_boxes( struct stuff *st, struct AMRbox *boxes, int boxlevelmask, Matrix Ttext, int oriented )
{
  static short arcs[] = {
	5, 4, 6, 2, 3, 1, 5, 7, 6,
	-1,
	7, 3,
	0, 1,
	0, 2,
	0, 4
  };
  struct AMRbox *box;
  int i;
  int blevels = (st->boxlevels > 1 ? st->boxlevels-1 : 1);
  float boxscale, s0, s1;
  int isscaled;

  glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  glLineWidth( st->boxlinewidth );
  glEnable( GL_BLEND );
  glDisable( GL_LINE_SMOOTH );

  /* Scan through the array, whose end is marked with a box at level -1. */
  for(box = boxes; box->level >= 0; box++) {
    boxscale = (box->level < 0) ? 1.0
	   : st->boxscale[ box->level>=MAXBOXLEV ? MAXBOXLEV-1 : box->level ];
    isscaled = (boxscale != 0 && boxscale != 1);
    if(isscaled) {
	s0 = .5*(1 + boxscale);
	s1 = .5*(1 - boxscale);
    }
    if((1 << box->level) & boxlevelmask) {
	int cval = RGBALPHA(st->boxcmap[
		    box->level<0 ? 0
		    : box->level>=st->boxncmap ?
			st->boxncmap-1 : box->level ],
		    0xFF);
	int colorme = 0;

	if(oriented)
	    cval = orientboxcolor;
	glColor4ubv( (GLubyte *)&cval );
	glBegin( GL_LINE_STRIP );
	for(i = 0; i < COUNT(arcs); i++) {
	    int vert = arcs[i];
	    if(vert < 0) {
		glEnd();
		glBegin( GL_LINES );
	    } else {

		if(oriented) {
		    if(vert == 0) {
			glColor4ubv( (GLubyte *)&cval );
			colorme = 1;
		    } else if(colorme) {
			glColor3f( vert&1?1:0, vert&2?1:0, vert&4?1:0 );
		    }
		}

		if(isscaled && !oriented && box->level >= 0) {
		    glVertex3f(
			vert&1	? s0*box->p0.x[0] + s1*box->p1.x[0]
				: s1*box->p0.x[0] + s0*box->p1.x[0],
			vert&2	? s0*box->p0.x[1] + s1*box->p1.x[1]
				: s1*box->p0.x[1] + s0*box->p1.x[1],
			vert&4	? s0*box->p0.x[2] + s1*box->p1.x[2]
				: s1*box->p0.x[2] + s0*box->p1.x[2] );
		} else {
		    glVertex3f(
			(vert&1 ? box->p1.x : box->p0.x)[0],
			(vert&2 ? box->p1.x : box->p0.x)[1],
			(vert&4 ? box->p1.x : box->p0.x)[2]
		    );
		}
	    }
	}
	glEnd();
	if(st->boxlabels && st->boxlabelscale != 0) {
	    float sz = .16 * vdist( &box->p0, &box->p1 ) / st->textsize;
	    char lbl[16];
	    glPushMatrix();
	    glTranslatef( .75*box->p0.x[0] + .25*box->p1.x[0],
			  .75*box->p0.x[1] + .25*box->p1.x[1],
			  .75*box->p0.x[2] + .25*box->p1.x[2]);
	    glMultMatrixf( Ttext.m );
	    sprintf(lbl, "%d", box->boxno);
	    sfStrDraw( lbl, sz, NULL );
	    glPopMatrix();
	}
    }
  }
  glLineWidth( 1 );
}

int specks_partial_pick_decode( struct stuff *st, int id,
			int nhits, int nents, GLuint *hitbuf,
			unsigned int *bestzp, struct specklist **slp,
			int *specknop, Point *pos )
{
  int i, hi, ns, slno;
  GLuint z0, bestz = *bestzp;
  int bestslno = 0, bestspeck = -1;
  struct specklist *sl, *slhead;

  if(st == NULL)
    return 0;

  for(hi = 0, i = 0; hi < nhits && i < nents; hi++, i += ns + 3) {
    ns = hitbuf[i];
    if(ns < 1 || ns > 16)
	break;			/* trouble */
    z0 = hitbuf[i+1];

    if(st->usepoly>1)		/* debug */
	printf(ns>1?"[%x %d/%d]":"[%x %d]", z0, hitbuf[i+3],hitbuf[i+4]);

    if(id == hitbuf[i+3] && bestz > z0 && ns > 1 && (slno = hitbuf[i+4]) > 0) {
	bestz = z0;
	bestslno = slno;
	bestspeck = (ns>2) ? hitbuf[i+5] : 0;
    }
  }
  if(bestslno <= 0)
    return 0;

  slhead = st->frame_sl;		/* st->sl as snapped by specks_ffn() */
  if(slhead == NULL) slhead = st->sl;	/* maybe there is no specks_ffn() */
  if(slhead == NULL) return 0;
  for(sl = slhead, slno = 1; sl != NULL; sl = sl->next, slno++) {
    if(slno == bestslno) {
	if(bestspeck < 0 || bestspeck >= sl->nspecks) {
	    msg("Bogus pick result: sl %x bestspeck %d of 0..%d!\n",
		sl, bestspeck, sl->nspecks-1);
	    return 0;
	}
	if(slp) *slp = sl;
	if(specknop) *specknop = bestspeck;
	if(pos) {
	    if(bestspeck < 0 || bestspeck >= sl->nspecks)
		bestspeck = 0;
	    *pos = NextSpeck( sl->specks, sl, bestspeck )->p;
	}
	*bestzp = bestz;
	return 1;
    }
  }
  return 0;
}


#if CAVE

void parti_seto2w( struct stuff *st, int objno, CONST Matrix *newTo2w ) {
    st->objTo2w = *newTo2w;	/* ignores objno -- there's only one right now! */
}

void parti_geto2w( struct stuff *st, int objno, Matrix *curTo2w ) {
    *curTo2w = st->objTo2w;	/* ignores objno -- there's only one right now! */
}
    
void specks_display( struct stuff *st )
{
    int showmenus;
    int menuhit = 0;
slevy's avatar
 
slevy committed
    int empty = st->ntimes == 0 && st->sl == NULL;
teuben's avatar
teuben committed
    static int hidescene;

    if( CAT_dsp_mode == APP_DSP_NORMAL ) {

#if !(MENU_IN_PPR)
	if( CAVEEye == CAVE_LEFT_EYE ) {
	    menu_check( pmenu, pmenu->cavewall );
	    menu_check( stubmenu, stubmenu->cavewall );

slevy's avatar
 
slevy committed
	    menu_sethidden( pmenu, st->hidemenu || empty );
	    menu_sethidden( stubmenu, st->hidemenu<=0 || empty );
teuben's avatar
teuben committed
	}
#endif

	glPushMatrix();
	/* The following only works if our transformation is exactly the
	 * Cave nav transform, i.e. if the CAT system hasn't
	 * translated us further; but that's true for this app.
	 */
	CAVENavInverseTransform();

	menu_draw( pmenu );

	if(!pmenu->hidden) {
	    menuhit = (pmenu->wallhit > 0);
	    menu_setpos( stubmenu, ment.menu->x0, ment.menu->y0 );
	}
	menu_draw( stubmenu );

	glPopMatrix();

    }

    if(hidescene && !menuhit)
	hidescene = 0;
    if(menuhit && *CAVEFramesPerSecond < st->menudemandfps)
	hidescene = 1;		/* Don't draw scene if too slow to operate menu easily */
    if(hidescene)
	return;

    glPushMatrix();
    glTranslatef( st->gtrans.x[0], st->gtrans.x[1], st->gtrans.x[2] );
    glScalef( st->gscale, st->gscale, st->gscale );
    glMultMatrixf( st->objTo2w.m );
    drawspecks( st );
    glPopMatrix();
}
#endif /*NOCAVE*/

static void addchunk( struct stuff *st, int nsp, int bytesperspeck,
			float scaledby, struct speck *sp, char *text,
			int outbytesperspeck )
{
    struct specklist **slp, *sl = NewN( struct specklist, 1 );
    memset( sl, 0, sizeof(*sl) );

    sl->bytesperspeck = outbytesperspeck;

    sl->specks = NewNSpeck( sl, nsp );
    sl->nspecks = nsp;
    sl->scaledby = scaledby;
    if(text) {
	sl->text = NewN( char, strlen(text)+1 );
	strcpy(sl->text, text);
    } else {
	sl->text = NULL;
    }
    if(bytesperspeck == outbytesperspeck) {
	memcpy( sl->specks, sp, nsp*sl->bytesperspeck );
    } else {
	int i;
	for(i = 0; i < nsp; i++)
	    memcpy(((char *)sl->specks) + i*outbytesperspeck,
		   ((char *)sp) + i*bytesperspeck,
		   outbytesperspeck);
    }
    slp = specks_timespecksptr(st, st->curdata, st->datatime);
    sl->next = *slp;
    *slp = sl;
    st->sl = specks_timespecks(st, st->curdata, st->curtime); /* in case it changed */
    sl->colorseq = -1;		/* Force recomputing colors */
    sl->sizeseq = -1;		/* Force recomputing sizes */
}

int specks_count( struct specklist *sl ) {
    int n;
    for(n = 0; sl != NULL; sl = sl->next)
	if(sl->text == NULL)
	    n += sl->nspecks * (sl->subsampled>0 ? sl->subsampled : 1);
    return n;
}

static float speckscale = 1;

void specks_read( struct stuff **stp, char *fname )
{
  FILE *f;
  char line[2048], oline[2048];
  char *key;
  struct stuff *st = *stp;
  int maxfields = 0;

slevy's avatar
 
slevy committed
#define SPECKCHUNK 2001	/* should be bigger, but too many machines have tiny stack areas! */
teuben's avatar
teuben committed
  struct speck sp[SPECKCHUNK];
  struct speck s;
  int i, nsp;
  int ignorefirst = 0;
  int lno = 0;

  if(fname == NULL) return;

  key = NewA( char, strlen(fname)+1 );	/* fname's space might get reused */
  strcpy(key, fname);
  fname = key;

  if((f = fopen(fname, "r")) == NULL) {
    msg("%s: can't open: %s", fname, strerror(errno));
    return;
  }

  s.rgba = RGBWHITE;
  s.size = 1;
  nsp = 0;

#define SPFLUSH() \
    if(nsp>0) {						\
	addchunk( st, nsp, sizeof(struct speck),	\
		speckscale, sp, NULL,			\
		maxfields > MAXVAL ? sizeof(struct speck) \
				   : SMALLSPECKSIZE( maxfields ) ); \
	nsp = maxfields = 0;				\
    }

  line[sizeof(line)-1] = '\1';
  while( fgets(line, sizeof(line), f) != NULL ) {
    lno++;
    if(line[sizeof(line)-1] != 1) {
	if(line[sizeof(line)-2] != '\n') {
	    while((i = getc(f)) != EOF && i != '\n')
		;
	}
	line[sizeof(line)-1] = '\1';
	line[sizeof(line)-2] = '\0';
	msg("%s line %d: truncated (>%d chars long!)", fname, lno, sizeof(line)-2);
    }

    strcpy(oline, line);

    key = strtok(line, separ);
    if(key == NULL || key[0] == '#' || key[0] == '\0')
	continue;

    if(nsp >= SPECKCHUNK || (isalnum(key[0]) && !isdigit(key[0]))) {
	SPFLUSH();
    }

    if(!strcmp(key, "include") || !strcmp(key, "read")) {
	float oldscale = speckscale;
	char *infname = strtok(NULL, separ);
	char *realfile = findfile( fname, infname );
	if(realfile == NULL) {
	    msg("%s: Can't find include-file %s", fname, infname);
	} else {
	    specks_read( &st, realfile );
	}
	speckscale = oldscale;

#ifdef USE_IEEEIO
    } else if(!strcmp(key, "ieee")) {
	int timestepno = st->datatime;
	char *realfile;
	char *s = strtok(NULL, separ);

	if(s != NULL && !strcmp(s, "-t")) {
	    s = strtok(NULL, separ);
	    if(s == NULL || (timestepno = getfloat(s, st->datatime)) < 0) {
		msg("ieee -t: expected timestepnumber(0-based), not %s", s);
		continue;
	    }
	    s = strtok(NULL, separ);
	}
	realfile = findfile( fname, s );
	if(realfile == NULL) {
	    msg("%s: ieee: can't find file %s", fname, s);
	} else {
	    /* Load IEEE dataset into time slot */
	    specks_ieee_open( st, realfile, st->curdata, timestepno );
	}
#endif

    } else if(!strcmp(key, "object")) {

	key = strtok(NULL, separ);
	parti_object( key, &st );

teuben's avatar
teuben committed
    } else if(!strcmp(key, "kira")) {
	char *s = strtok(NULL, separ);
slevy's avatar
 
slevy committed
	char *realfile = findfile( fname, s );
teuben's avatar
teuben committed
	char *verbose = strtok(NULL, separ);
#ifndef USE_KIRA
	msg("Need to recompile with -DUSE_KIRA");
#else
slevy's avatar
 
slevy committed
	open_parti(st, realfile, verbose ? atoi(verbose) : 0);
teuben's avatar
teuben committed
#endif

slevy's avatar
 
slevy committed

teuben's avatar
teuben committed
    } else if(!strcmp(key, "sdb")) {
	char *s = strtok(NULL, separ);
	int tno = st->datatime;
	char *realfile;
	if(s != NULL && !strcmp(s, "-t")) {
	    if((tno = getfloat(s=strtok(NULL, separ), st->datatime)) < 0) {
		msg("sdb -t: expected timestepnumber(0-based), not %s", s);
		continue;
	    }
	    s = strtok(NULL, separ);
	}
	realfile = findfile( fname, s );
	if(realfile == NULL) {
	    msg("%s: sdb: can't find file %s", fname, s);
	} else {
	    specks_read_sdb( st, realfile, tno );
	}

    } else if(!strcmp(key, "sdbvars")) {
	char *s = strtok(NULL, separ);
	if(s != NULL) {
	    if(s[strspn(s, "mMcrogtxyzSn")] != '\0') {
		msg("sdbvars: pattern must contain only chars from: mMcrogtxyzSn, not %s",
			s);
	    } else {
		if(st->sdbvars) Free(st->sdbvars);
		st->sdbvars = shmstrdup(s);
	    }
	} else {
	    msg("sdbvars %s", st->sdbvars);
	}

    } else if(!strcmp(key, "box") || !strcmp(key, "boxes")) {
	char *args[16], **av;
	Point cen, rad;
	struct AMRbox box;
	int ac, k;

	int tno = -1;
	for(ac = 0; ac < COUNT(args) && (args[ac] = strtok(NULL, separ)) != NULL; ac++)
	    ;

	box.boxno = -1;
	box.level = 0;

	av = args;
	while(ac > 2 && av[0][0] == '-') {
	    if(av[0][1] == 'n' && sscanf(av[1], "%d", &box.boxno)>0)
		ac -= 2, av += 2;
	    else if(av[0][1] == 'l' && sscanf(av[1], "%d", &box.level)>0)
		ac -= 2, av += 2;
	    else if(av[0][1] == 't' && (tno = getfloat(av[1], st->datatime)) >= 0)
		ac -= 2, av += 2;
	    else
		break;
	}
	if(ac == 2 &&
	     3==sscanf(av[0], "%f%*c%f%*c%f", &cen.x[0],&cen.x[1],&cen.x[2]) &&
	     0<(k=sscanf(av[1], "%f%*c%f%*c%f", &rad.x[0],&rad.x[1],&rad.x[2]))) {
	    if(k<3) rad.x[1] = rad.x[2] = rad.x[0];	/* if scalar radius */
	    vsub(&box.p0, &cen, &rad);
	    vadd(&box.p1, &cen, &rad);
	    specks_add_box( st, &box, tno );

	} else if(ac == 3 &&
		    2==sscanf(av[0], "%f%*c%f",&box.p0.x[0],&box.p1.x[0]) &&
		    2==sscanf(av[1], "%f%*c%f",&box.p0.x[1],&box.p1.x[1]) &&
		    2==sscanf(av[2], "%f%*c%f",&box.p0.x[2],&box.p1.x[2])) {

	    specks_add_box( st, &box, tno );

	} else if(ac == 6) {
	    for(k=3; --k >= 0; ) {
		if(sscanf(av[k], "%f", &box.p0.x[k])<=0
			|| sscanf(av[k+3], "%f", &box.p1.x[k])<=0)
		    break;
	    }
	    if(k<0)
		specks_add_box( st, &box, tno );
	    else
		msg("box: expected xmin ymin zmin  xmax ymax zmax  -or- xmin,xmax ymin,ymax zmin,zmax -or- xcen,ycen,zcen xrad,yrad,zrad");

	} else if(ac == 1) {
	    char *realfile = findfile( fname, av[0] );
	    if(realfile == NULL) {
		msg("%s: boxes: can't find file %s", fname, av[0]);
	    } else {
		specks_read_boxes( st, realfile, tno );
	    }
	} else {
	    msg("usage: box [-t timestep] AMRboxfile  -or-");
	    msg("  box [-t time] [-n boxno] [-l level] xcen,ycen,zcen  xradius,yradius,zradius  -or-");
	    msg("  box [options]  xmin ymin zmin  xmax ymax zmax");
	    msg(" options: -n <boxno>  box number, for \"gobox\" and \"boxlabel\" cmds (dflt -1)");
	    msg("   -l <boxlevel>      level-number (0..31) for \"showboxlevel\" cmds (dflt 0)");
	    msg("   -t <time>		timestep, if animated; default eternal,");
	    msg("			or (for animated AMRboxfile) start time 0");
	}

	
    } else if(!strncmp(key, "annot", 5)) {
	static char ausage[] = "Usage: annot [-t timestep]  string...";
	char *es, *s = strtok(NULL, "\r\n");
	int tno = st->datatime;

	if(s == NULL) {
	    msg(ausage);
	    continue;
	}
	while(*s && isspace(*s)) s++;
	if(!strncmp(s, "-t", 2)) {
	    if((tno = getfloat(s+2, st->datatime)) < 0) {
		msg(ausage);
		continue;
	    }
	    for(s += 2; isspace(*s) || *s=='+' || *s=='-'; s++) ;
	    while(isdigit(*s)) s++;
	    while(*s && isspace(*s)) s++;
	}
	if(*s == '"') s++;
	specks_add_annotation( st, s, tno );

    } else if(!strcmp(key, "size")) {
	key = strtok(NULL, separ);
	if(key) s.size = atof(key);

    } else if(!strcmp(key, "scale")) {	/* "scale" in data file, not as VIRDIR command */
	float v;
	key = strtok(NULL, separ);
	if(key != NULL && (v = atof(key)) != 0)
	    st->spacescale = speckscale = v;

    } else if(!strcmp(key, "tfm")) {
	int inv = 0;
	int mul = 0;
	int hpr = 0;
	int any, more;
	float scl;
	Matrix ot, t;
	Point xyz;
	float aer[3];
	key = strtok(NULL, "\n");
	if(key == NULL) key = "";
	for(more = 1; *key != '\0' && more; key++) {
	  switch(*key) {
	  case '*': mul = 1; break;
	  case '/': inv = 1; break;
	  case 'h': case 'p': case 'r': hpr = 1; break;
	  case ' ': case '\t': break;
	  default: more = 0; key--; break;
	  }
	}

	parti_geto2w( st, parti_object( NULL, &st ), &ot );

	switch(any = sscanf(key, "%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f",
			&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])) {
	
	case 1:
		scl = t.m[0];
		t = Tidentity;
		t.m[0*4+0] = t.m[1*4+1] = t.m[2*4+2] = scl;
		break;
	case 6:
	case 7:		/* ignore fovy if included */
		xyz = *(Point *)&t.m[0];
		if(hpr) {
		    aer[0] = t.m[3], aer[1] = t.m[4], aer[2] = t.m[5];
		} else {
		    /* Note we permute: px py pz rx ry rz == px py pz e a r */
		    aer[1] = t.m[3], aer[0] = t.m[4], aer[2] = t.m[5];
		}
		xyzaer2tfm( &t, &xyz, aer );
		break;

	case 16: break;
	case 0:  t = ot; break;
	default:
	    msg("Usage: tfm: expected 16 numbers");
	    continue;
	}
	if(any) {
	    if(inv)
		eucinv( &t, &t );
	    if(mul)
		mmmul( &t, &ot, &t );
	    if(strchr(key, '*'))
		mmmul( &t, &t, &ot );

	    /* with trailing '=', just print result without assignment */

	    if(NULL==strchr(key, '='))
		parti_seto2w( st, parti_object( NULL, &st ), &t );
	}

	tfm2xyzaer( &xyz, aer, &t );
	msg("obj2w:  x y z rx ry rz   %g %g %g  %g %g %g",
		xyz.x[0],xyz.x[1],xyz.x[2],  aer[1],aer[0],aer[2] );
	for(i = 0; i < 3; i++)
	    msg(" %10.7f %10.7f %10.7f %10.7f", t.m[i*4+0],t.m[i*4+1],t.m[i*4+2],t.m[i*4+3]);
	msg(" %10.7g %10.7g %10.7g %10.7g", t.m[3*4+0],t.m[3*4+1],t.m[3*4+2],t.m[3*4+3]);
	eucinv( &t, &t );
	tfm2xyzaer( &xyz, aer, &t );
	msg("w2obj:  x y z rx ry rz   %g %g %g  %g %g %g",
		xyz.x[0],xyz.x[1],xyz.x[2],  aer[1],aer[0],aer[2] );
	for(i = 0; i < 3; i++)
	    msg(" %10.7f %10.7f %10.7f %10.7f", t.m[i*4+0],t.m[i*4+1],t.m[i*4+2],t.m[i*4+3]);
	msg(" %10.7g %10.7g %10.7g %10.7g", t.m[3*4+0],t.m[3*4+1],t.m[3*4+2],t.m[3*4+3]);


    } else if(!strcmp(key, "eval") || !strcmp(key, "feed")
				   || !strcasecmp(key, "VIRDIR")) {
	char *av[128];
	int ac;
	char *cmd = strtok(NULL, "\n");
	char *savedcmd = NewA( char, strlen(cmd) + 1 );

	strcpy(savedcmd, cmd);
	key = strtok(cmd, separ);
	for(ac = 0; ac < 127 && key != NULL; ac++) {
	    av[ac] = key;
	    key = strtok(NULL, separ);
	}
	av[ac] = NULL;
#if CAVE
	if(0 == specks_parse_args( &st, ac, av )) 
	    VIDI_queue_commandstr( savedcmd );

#else /* non-CAVE */
	specks_parse_args( &st, ac, av );

#endif /* non-CAVE */

    } else if(!strcmp(key, "ignorefirst") || !strcmp(key, "ignorepgc")) {
	ignorefirst = 1;

    } else if(!strcmp(key, "filepath")) {
	int m, k = 0;
	char **oldpath = getfiledirs();
	char *path[64];
	while(k < 64-1 && (key = strtok(NULL, ": \t\r\n")) != NULL) {
	    if(!strcmp(key, "+")) {
		for(m = 0; k<64-1&&oldpath!=NULL&&oldpath[m]!=NULL; m++)
		    path[k++] = strdup(oldpath[m]);
	    } else {
		path[k++] = strdup(key);
	    }
	}
	path[k] = NULL;
	if(k > 0) filedirs(path);
	while(--k >= 0) free(path[k]);

    } else if(!strcmp(key, "texture")) {
slevy's avatar
 
slevy committed
	int txno = 0;
teuben's avatar
teuben committed
	int txflags = TXF_SCLAMP | TXF_TCLAMP;
	int txapply = TXF_DECAL;
	int qual = 7;
	char *txfname;
	key = strtok(NULL, separ);
slevy's avatar
 
slevy committed
	sscanf(key, "%d", &txno);
	while(key != NULL && (key[0] == '-' || key[0] == '+')) {
	    int tqual = (strchr(key,'m') ? 4:0) |
			(strchr(key,'l') ? 2:0) |
			(strchr(key,'n') ? 1:0);
	    if(key[0] == '-') qual &= ~tqual;
	    else qual |= tqual;
	    if(strchr(key,'a')) txflags |= TXF_ALPHA;
	    if(strchr(key,'i')) txflags |= TXF_INTENSITY;
	    if(strchr(key, 'A')) txflags |= TXF_ADD;
teuben's avatar
teuben committed
	    if(strchr(key, 'M')) txapply = TXF_MODULATE;
	    if(strchr(key, 'D')) txapply = TXF_DECAL;
	    if(strchr(key, 'B')) txapply = TXF_BLEND;
	    key = strtok(NULL, separ);
	}
slevy's avatar
 
slevy committed
	if(key == NULL || (txno == 0 && sscanf(key, "%d", &txno) <= 0)
teuben's avatar
teuben committed
	   || (txfname = strtok(NULL, separ)) == NULL) {
slevy's avatar
 
slevy committed
		msg("Expected ``texture [-lmnaMDB] txno file.sgi'', got %s", oline);
		msg(" opts: -l(inear) -m(ipmap) -n(earest) -i(intensity) -a(lpha) -A(dd) -M(odulate)|-D(ecal)|-B(lend))");
teuben's avatar
teuben committed
2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372
	} else {
	    txaddentry( &st->textures, &st->ntextures, fname, txno, txfname, txapply, txflags, qual );
	}

    } else if(!strcmp(key, "polyorivar")) {
	if((key = strtok(NULL, separ)) != NULL)
	    sscanf(key, "%d", &st->polyorivar0);

    } else if(!strcmp(key, "texturevar")) {
	if((key = strtok(NULL, separ)) != NULL)
	    sscanf(key, "%d", &st->texturevar);

    } else if(!strncmp(key, "coord", 5) || !strncmp(key, "altcoord", 8)) {
	float *t = &st->altcoord[0].w2coord.m[0];
	key = strtok(NULL, separ);
	if(key == NULL) {
	    msg("coord %s  %g %g %g %g  %g %g %g %g  %g %g %g %g  %g %g %g %g",
		st->altcoord[0].name, t[0],t[1],t[2],t[3],
		t[4],t[5],t[6],t[7],
		t[8],t[9],t[10],t[11],
		t[12],t[13],t[14],t[15]);
	} else {
	    sprintf(st->altcoord[0].name, "%.9s", key);
	    key = strtok(NULL, "\n");
	    if(key != NULL && 16 != sscanf(key, "%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f",
			t,t+1,t+2,t+3, t+4,t+5,t+6,t+7, t+8,t+9,t+10,t+11,
			t+12,t+13,t+14,t+15)) {
		key = NULL;
	    }
	    if(key == NULL) {
		msg("expected \"coord\" name  ... 16 world-to-coord tfm floats (GL order) ...");
	    }
	}
    } else if(!strncmp(key, "dataset", 7)) {
	char name[12];
	int indexno;
	if((key = strtok(NULL,"\n")) == NULL ||
		sscanf(key, "%d %11s", &indexno, name) < 2
		|| indexno < 0 || indexno >= MAXFILES) {
	    msg("%s: expected ``dataset <indexno> <datasetname>'' with 0<=indexno<=%d",
		fname, MAXFILES-1);
	    continue;
	}
	strncpyt(st->dataname[indexno], name, sizeof(st->dataname[indexno]));
	st->curdata = indexno;

    } else if(!strncmp(key, "datavar", 7)) {
	int varno, nargs;
	struct valdesc vd;

	if((key = strtok(NULL, "\n")) == NULL ||
		(nargs = sscanf(key, "%d %19s %f %f", &varno, vd.name, &vd.min, &vd.max)) < 2
		|| varno < 0 || varno >= MAXVAL) {
	    msg("%s: expected ``datavar <indexno> <variablename>'' with 0<=indexno<=%d",
		fname, MAXVAL-1);
	    continue;
	}
	strcpy(st->vdesc[st->curdata][varno].name, vd.name);
	if(nargs == 4) {
	    st->vdesc[st->curdata][varno].min = vd.min;
	    st->vdesc[st->curdata][varno].max = vd.max;
	}

    } else if(!strcmp(key, "datatime")) {
	int newt = st->curtime;	/* so e.g. "datatime now" => apply to current data */
	if((key = strtok(NULL, separ)) == NULL) {
	    msg("%s: datatime %d", fname, st->datatime);
	    continue;
	}
	sscanf(key, "%d", &newt);
	if(newt < 0) {
	    msg("%s: datatime %s: timestep must be >= 0", fname, key);
	    continue;
	}
	if(newt == st->datatime)	/* no need to change anything */
	    continue;
	if(nsp > 0) {			/* else flush accumulated specks */
	    addchunk( st, nsp, sizeof(struct speck), 
		speckscale, sp, NULL,
		maxfields > MAXVAL ? sizeof(struct speck)
				   : SMALLSPECKSIZE( maxfields ) );
	    nsp = maxfields = 0;
	}
	st->datatime = newt;

    } else {
	struct valdesc *vdp;

	i = 0;
	if(!ignorefirst)
	    s.p.x[i++] = atof(key) * speckscale;
	for( ; i < 3 && (key = strtok(NULL,separ)) != NULL; i++) {
	    s.p.x[i] = atof(key) * speckscale;
	}
	if(i < 3)
	    continue;
	vdp = &st->vdesc[st->curdata][0];
	for(i = 0; (key = strtok(NULL,separ)) != NULL; i++) {
	    if(key[0] == 't') break;	/* "text" */
	    if(key[0] == '#') break;
	    if(i < COUNT(s.val)) {
		s.val[i] = atof(key);
		if(vdp->nsamples++ == 0) {
		    vdp->min = vdp->max = s.val[i];
		} else {
		    if(vdp->min > s.val[i]) vdp->min = s.val[i];
		    else if(vdp->max < s.val[i]) vdp->max = s.val[i];
		}
		vdp->sum += s.val[i];
		vdp->nsamples++;
		vdp->mean = vdp->sum / vdp->nsamples;
		vdp++;
	    }
	}
	if(maxfields < i) maxfields = i;
	s.title[0] = '\0';
	if(key && key[0] == '#') {
	    key = strtok(NULL, "\r\n");
	    if(key) {
		while(isspace(key[0])) key++;
		for(i = strlen(key); --i >= 0 && isspace(key[i]); )
		    key[i] = '\0';
		sprintf(s.title, "%.*s", (int)(sizeof(s.title)-1), key);
		maxfields = MAXVAL+1;	/* "keep title too" */
	    }
	}

	sp[nsp++] = s;

	if(key != NULL && key[0] == 't') {	/* "text" */
	    s.size = 1;
	    key = strtok(NULL, "\n");
	    if(key && !strncmp(key, "-size", 5)) {
		s.size = strtod( key+5, &key );
		if(*key) key++;
		if(s.size == 0) s.size = 1;
	    }
	    addchunk( st, 1, SMALLSPECKSIZE(0), speckscale, &s, key,
			     SMALLSPECKSIZE(0) );
	    nsp--;
	}
    }
  }
  fclose(f);
  SPFLUSH();
  *stp = st;
}

/* Add a static (eternal) box to display list */
int specks_add_box( struct stuff *st, struct AMRbox *box, int timestep )
{
  int i;
  if(box->level >= 0 && st->boxlevels <= box->level)
    st->boxlevels = box->level+1;
  for(i = 0; i < st->staticboxroom && st->staticboxes[i].level >= 0; i++) {
    if(box->boxno != -1 && box->boxno == st->staticboxes[i].boxno) {
	st->staticboxes[i] = *box;
	return ~i;
    }
  }

  if(i >= st->staticboxroom-1) {
    st->staticboxroom = (st->staticboxroom>0) ? st->staticboxroom*2 : 12;
    st->staticboxes = RenewN( st->staticboxes, struct AMRbox, st->staticboxroom );
  }
  st->staticboxes[i] = *box;
  st->staticboxes[i+1].level = -1;
  return i;
}


void specks_read_boxes( struct stuff *st, char *fname, int timebase )
{
  FILE *f = fopen(fname, "r");
  char line[1024];
  int ntimes = -1, maxbox = -1, curbox = 0, curtime = 0, level = -1, i;
  int lno = 0;
  int boxseq, boxno;
  struct AMRbox *boxes = NULL;
  struct AMRbox box;

  if(timebase < 0)	/* if unspecified time, start at first timestep */
    timebase = 0;

  if(f == NULL) {
    msg("Can't open AMRboxes file %s (as from hier2boxes.pl)", fname);
    return;
  }
  while(fgets(line, sizeof(line), f) != NULL) {
    lno++;
    boxno = -1;
    if(sscanf(line, "%f%f%f %f%f%f # %d",
		&box.p0.x[0],&box.p0.x[1],&box.p0.x[2],
		&box.p1.x[0],&box.p1.x[1],&box.p1.x[2], &boxno) >= 6) {
	for(i = 0; i < 3; i++) {
	    box.p0.x[i] *= st->spacescale;
	    box.p1.x[i] *= st->spacescale;
	}
	if(boxno != -1)
	    box.boxno = boxno;
	else
	    box.boxno = boxseq++;
	if(curbox < maxbox && boxes != NULL && level >= 0) {
	    boxes[curbox] = box;
	    curbox++;
	} else {
	    msg("%s line %d: Excess box (only expected %d at timestep %d)",
		fname, lno, maxbox, curtime);
	}
    }
    else if(sscanf(line, "AMRboxes %d timesteps", &ntimes) > 0 && ntimes >= 0) {
	int needtimes = ntimes + (timebase<0 ? 0 : timebase);
	if(st->boxtimes <= needtimes+1) {
	    needtimes += st->boxtimes + 15;	/* leave lots of extra room */
	    st->boxes = RenewN( st->boxes, struct AMRbox *, needtimes );
	    memset( &st->boxes[st->boxtimes], 0,
			(needtimes - st->boxtimes) * sizeof(struct AMRbox *) );
	    if(st->boxtimes < ntimes+timebase)
		st->boxtimes = ntimes+timebase;
	}
	boxes = NULL;
    }
    else if(sscanf(line, "timestep %d %d grids", &curtime, &maxbox) == 2) {
	boxseq = 1;
	if(ntimes < 0) {
	    msg(
"%s line %d: ``AMRboxes N timesteps'' must precede ``timestep'' lines!",
		    fname, lno);
	    curtime = -1;
	    boxes = NULL;
	    continue;
	} else if(curtime<0 || curtime>=ntimes || maxbox<0) {
	    msg("%s line %d: bad timestep number", fname, lno); 
	    curtime = -1;
	    boxes = NULL;
	    continue;
	}
	if((boxes = st->boxes[curtime+timebase]) != NULL) {
	    msg("%s line %d: Respecifying timestep %d",
		fname, lno, curtime);
	    Free( st->boxes[curtime+timebase] );
	}
	boxes = NewN( struct AMRbox, maxbox+1 );
	for(curbox = 0; curbox <= maxbox; curbox++)
	    boxes[curbox].level = -(maxbox+1);
	st->boxes[curtime+timebase] = boxes;

	/* Ensure that known span of time includes this timestep */
	(void) specks_timespecksptr( st, st->curdata, curtime+timebase );

	curbox = 0;
    }
    else if(sscanf(line, "level %d", &level) > 0) {
	if(boxes == NULL) {
	    msg("%s line %d: Must specify timestep before level",
		fname, lno);
	    level = -1;
	}
	if(st->boxlevels <= level)
	    st->boxlevels = level+1;
	box.level = level;
    }
  }
  fclose(f);

  /* st->boxlevelmask |= (1 << st->boxlevels) - 1;
   * Don't do this -- it turns on all boxlevels whenever we get a new box!
   */
}

static struct AMRbox *findboxno( struct AMRbox *box, int boxno, int *count, int *bmin, int *bmax ) {
  if(box == NULL) return NULL;
  while(box->level >= 0) {
    if(box->boxno == boxno)
	return box;
    if(*bmin > box->boxno) *bmin = box->boxno;
    if(*bmax < box->boxno) *bmax = box->boxno;
    ++*count;
    box++;
  } 
  return NULL;
}

int specks_gobox( struct stuff *st, int boxno, int argc, char *argv[] )
{
  Point pmid;
  float sz, scale;
  char cmd[128];
  int bmin = 1<<30, bmax = -1<<30;
  int count = 0;
  struct AMRbox *box = NULL;

  if(st->boxes && st->curtime < st->boxtimes)
    box = findboxno( st->boxes[st->curtime], boxno, &count, &bmin, &bmax );
  if(box == NULL)
    box = findboxno( st->staticboxes, boxno, &count, &bmin, &bmax );

  if(box == NULL) {
    if(count == 0)
	msg("No AMR boxes for timestep %d", st->curtime);
    else
	msg("%d AMR boxes (numbered %d..%d) for timestep %d, but no box number %d",
	    count, bmin, bmax, st->curtime, boxno);
    return 0;
  }

  vlerp( &pmid, .5*st->gscale, &box->p0, &box->p1 );
  set_interest_point( &pmid );

  sz = vdist( &box->p0, &box->p1 );
  sprintf(cmd, "%.1g", st->gscale * st->goboxscale * sz);
		/* round to 1 decimal */
  scale = atof(cmd);
#if CAVE
  {
    Point pmid, fwd, headv, pos, offset, newpos;
    static Point zvec = {0,0,1};
    CAVENavConvertVectorCAVEToWorld( zvec.x, fwd.x );
    CAVEGetPosition( CAVE_HEAD, headv.x );
    headv.x[2] -= 10.0;	/* a point in front of head, in CAVE coords */
    CAVENavConvertVectorCAVEToWorld( headv.x, offset.x );
    msg("scale %g caveunit %g offsetW %g %g %g", scale, vlength(&fwd), offset.x[0],offset.x[1],offset.x[2]);
    vsadd( &newpos, &pmid, -scale / vlength(&fwd), &offset );
    sprintf(cmd,
	sz > 0	? "jumpto %g %g %g  . . .  %g"
		: "jumpto %g %g %g",
	newpos.x[0], newpos.x[1], newpos.x[2], scale);
    VIDI_queue_commandstr( cmd );
  }
#endif

  return 1;
}

void specks_read_cmap( struct stuff *st, char *fname, int *ncmapp, int **cmapp )
{
  int i;
  char *cp;
  char line[256];
  int k, count = 0;
  int *tcmap;
  float fr,fg,fb,fa;
  int big = 0;
  int lno = 0;
  int ncmap = 0;
  int cval;
  int csrc;

  FILE *f = fopen(fname, "r");
  if(f == NULL) {
    msg("Can't open colormap file %s", fname);
    return;
  }
  tcmap = NULL;

  ncmap = 0;
  while(fgets(line, sizeof(line), f) != NULL) {
    lno++;
    cp = line;
   rescan:
    for( ; isspace(*cp); cp++)
	;
    if(*cp == '\0' || *cp == '#')
	continue;
    fa = 1.0;
    k = sscanf(cp, "%f%f%f", &fr,&fg,&fb/*,&fa*/);
    if(k == 1) {
	if(count == 0 && fr == (int)fr && fr > 0) {
	    count = fr;
	    if(count >= 1 && count < 1000000)		/* OK */
		continue;
	    msg("Unreasonable number of colormap entries claimed in header");
	    /* and fall into error case */
	} else {
	    while(isspace(*cp) || isdigit(*cp)) cp++;
	    if(*cp == ':') {
		ncmap = fr;
		if(ncmap < 0) {
		    ncmap = 0;		/* don't continue -- fall into error */
		} else if(ncmap >= count) {
		    msg("Colormap index %g exceeds size %d given in header",
			fr, count);	/* don't continue -- ditto */
		} else {
		    cp++;
		    goto rescan;		/* All's well */
		}
	    }
	}
    } else if(k >= 3) {
	if(fr > 2 || fg > 2 || fb > 2 || fa > 2)
	    big = 1;
	if(!big) {
	    fr *= 255; fg *= 255; fb *= 255; /*fa *= 255;*/
	}
	cval = PACKRGBA( (int)fr, (int)fg, (int)fb, 0 );
	if(tcmap == NULL) {
	    tcmap = NewA( int, count );
	    /* Fill unused entries with gray */
	    memset(tcmap, 0x80, count*sizeof(int));
	}
	tcmap[ncmap>=count ? count-1 : ncmap<0 ? 0 : ncmap] = cval;
	ncmap++;
	if(ncmap >= count)
	    break;
	continue;

    } else if(sscanf(cp, "=%d", &csrc) > 0) {
	if(csrc < 0 || csrc >= count || ncmap < 0 || ncmap >= count || tcmap == NULL) {
	    msg("Colormap index out of range: %d or %d isn't in 0..%d",
		ncmap, csrc, count-1);
	} else {
	    tcmap[ncmap++] = tcmap[csrc];
	    continue;
	}
	/* Fall through into error */
    }
    msg("Trouble reading colormap file %s: bad line %d: %s",
		fname, lno, line);
    fclose(f);
    return;
  }
  fclose(f);
  if(tcmap == NULL)
    return;

  *ncmapp = count;
  if(*ncmapp < 2) *ncmapp = 2;
  *cmapp = RenewN( *cmapp, int, *ncmapp );
  memcpy( *cmapp, tcmap, count*sizeof(int) );
  if(count < 2)
    (*cmapp)[1] = (*cmapp)[0];
}


#if CAVEMENU
void set_psize( float psize, MenuEnt *me, void *st ) {
   char str[20];
   ((struct stuff *)st)->psize = psize;
   sprintf(str, "pointsize %.3g", psize);
   menu_settitle( me, str );
}

	/* set_tknob() only used if PARTIMENU mentions "survey" */
void set_tknob( float val, MenuEnt *me, void *vtb ) {
  char str[128];
  struct stuff *st = ((struct boxleveler *)vtb)->st;
  int self = ((struct boxleveler *)vtb)->level;
  static int mod[3] = { 1, 4, 16 };
  static char *fmt[3] = {
	"StarMassEjFrac [%d/4]",
	"SNFeedback     [%d/4]",
	"StarEfficiency [%d/4]",
  };
  int range = 4, mine, rest, above;
  above = mod[self] * range;
  rest = st->curtime % mod[self] + (st->curtime / above) * above;
  mine = (st->curtime / mod[self]) % range;
  if(me->state == ME_PICKED || me->state == ME_HELD) {
    mine = val;
    if(mine < 0) mine = 0; else if(mine >= range) mine = range-1;
slevy's avatar
 
slevy committed
    clock_set_time( st->clk, rest + mine*mod[self] );
    clock_set_running( st->clk, 0 );
teuben's avatar
teuben committed
  }
  sprintf(str, fmt[self], mine+1);
  menu_settitle( me, str );
}

void set_step( float time0, MenuEnt *me, void *vst ) {
   char str[32];
   struct stuff *st = (struct stuff *)vst;
   if(me->state == ME_PICKED) {
slevy's avatar
 
slevy committed
	time0 += clock_fwd(st->clk);
teuben's avatar
teuben committed
	st->timeplay = 0;
	me->state = ME_HELD;
   }
slevy's avatar
 
slevy committed
   clock_set_time( st->clk, me->val = (int) time0 );
teuben's avatar
teuben committed
   /* NOTE we use the static values snapped by the framefunction.
    * st->curtime etc. might have changed since then.
    */
   sprintf(str, "step %2d of 0..%d", st->frame_time, st->ntimes - 1);
   if(st->fetching > 0)
	sprintf(str+strlen(str), " (loading %d)", st->fetchtime);
   menu_settitle( me, str );
}

void set_fwd( int fwd, MenuEnt *me, void *st ) {
slevy's avatar
 
slevy committed
   clock_set_fwd( ((struct stuff *)st)->clk, fwd );
teuben's avatar
teuben committed
   menu_settitle( me, fwd ? ">>> fwd >>>" : "<<< rev <<<" );
}

void set_viewall( int all, MenuEnt *me, void *vst ) {
  struct stuff *st = (struct stuff *)vst;
  if(all) {
    strcpy( st->vdcmd, "\002jump all" );
    menu_settitle( me, "view all" );
  }
  else {
    strcpy( st->vdcmd, "\002jump peak" );
    menu_settitle( me, "view peak" );
  }
}

float get_nav_scale(void)
{
  float nav[4][4];
  CAVENavGetMatrix( nav );
  return 1 / sqrtf( vdot( (Point *)&nav[0][0], (Point *)&nav[0][0] ) );
}

void set_scale( float logscale, MenuEnt *me, void *vst ) {
  struct stuff *st = (struct stuff *)vst;
  float scl = pow(10., logscale);
  char str[64];

  if(me->state == ME_PICKED || me->state == ME_HELD) {
    sprintf( st->vdcmd, "\002scale %g", scl);
  } else {
    scl = get_nav_scale();
    me->val = scl<=0 ? -6 : log10( scl );
  }
  sprintf( str, "scale %.4g", scl );
  menu_settitle( me, str );
}


void set_dataset( int which, MenuEnt *me, void *vst ) {
   char str[32];
   struct stuff *st = (struct stuff *)vst;
   if(which < 0 || which >= st->ndata || which >= MAXFILES) which = 0;
   st->curdata = which;
   sprintf(str, "viewing %s (%d)", st->dataname[which], which);
   menu_settitle( me, str );
}

void set_lumvar( int lumvar, MenuEnt *me, void *vst ) {
   char str[80];
   struct stuff *st = (struct stuff *)vst;
   struct valdesc *vd;
   if(lumvar < 0 || lumvar >= MAXVAL+1) lumvar = 0;
   st->sizedby = lumvar;
   vd = &st->vdesc[st->curdata][lumvar];
   if(vd->lmin == vd->lmax) vd->lmin = vd->min, vd->lmax = vd->max;
   sprintf(str, "lum by %s (%d) [%.3g..%.3g]", vd->name, lumvar, vd->lmin, vd->lmax);
   menu_settitle( me, str );
}

void set_colorvar( int colorvar, MenuEnt *me, void *vst ) {
   char str[80];
   struct stuff *st = (struct stuff *)vst;
   struct valdesc *vd;
   if(colorvar < 0 || colorvar >= MAXVAL+1) colorvar = 0;
   st->coloredby = colorvar;
   vd = &st->vdesc[st->curdata][colorvar];
   if(vd->cmin == vd->cmax) vd->cmin = vd->min, vd->cmax = vd->max;
   sprintf(str, "color by %s (%d) [%.3g..%.3g]", vd->name,
		colorvar, vd->cmin, vd->cmax);
   menu_settitle( me, str );
}

void set_slum( float slum, MenuEnt *me, void *vst ) {
   char str[32];
   struct stuff *st = (struct stuff *)vst;
   st->vdesc[st->curdata][st->sizedby].lum = slum;
   sprintf(str, slum == 0 ? "scale-lum <unset>" : "scale-lum %.2g", slum);
   menu_settitle( me, str );
}

void set_speed( float speed, MenuEnt *me, void *vst ) {
   char str[32];
   struct stuff *st = (struct stuff *)vst;
teuben's avatar
teuben committed
   specks_set_speed( st, speed );
teuben's avatar
teuben committed
   sprintf(str, "speed %.1f steps/anim sec", speed);
   menu_settitle( me, str );
}

void set_fspeed( float speed, MenuEnt *me, void *vst ) {
   char str[64];
   struct stuff *st = (struct stuff *)vst;
teuben's avatar
teuben committed
   specks_set_fspeed( st, speed );
teuben's avatar
teuben committed
   if(me->state == ME_PICKED) {
slevy's avatar
 
slevy committed
	clock_set_running( st->clk, !clock_running(st->clk) );
teuben's avatar
teuben committed
	me->state = ME_HELD;	/* only once */
   }
slevy's avatar
 
slevy committed
   sprintf(str, "fspeed %.1f steps/real sec (%s)", speed,
			clock_running(st->clk) ? "running" : "stopped");
teuben's avatar
teuben committed
   menu_settitle( me, str );
}

void set_seedata( int which, MenuEnt *me, void *vst ) {
   struct stuff *st = (struct stuff *)vst;
   char str[64];
   if(which >= st->ndata) which = st->ndata - 1;
   if(which < 0) which = 0;
   st->curdata = which;
   sprintf(str, "see %.24s (%d)", st->dataname[which], which);
   menu_settitle( me, str );
}

void set_every( float every, MenuEnt *me, void *vst ) {
   char str[32];
   struct stuff *st = (struct stuff *)vst;
   if(every < 1) every = 1;
   st->subsample = me->val = every;
   sprintf(str, "every %dth p'cle (of %d)", (int)every, specks_count(st->sl));
   menu_settitle( me, str );
}

void set_menu( int on, MenuEnt *me, void *vst ) {
   struct stuff *st = (struct stuff *)vst;
slevy's avatar
 
slevy committed
   int empty = st->ntimes == 0 && st->sl == NULL;
teuben's avatar
teuben committed
   if(me->state == ME_PICKED)
	st->hidemenu = !st->hidemenu;
slevy's avatar
 
slevy committed
   menu_sethidden( pmenu, st->hidemenu || empty );
   menu_sethidden( stubmenu, st->hidemenu<=0 || empty );
teuben's avatar
teuben committed
3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860
}

void set_allboxes( int on, MenuEnt *me, void *vst ) {
   struct stuff *st = (struct stuff *)vst;
   st->useboxes = on;
   menu_settitle( me, on==2 ? "Boxes ONLY" :
			on ? "Boxes ON" : "Boxes off" );
}

void set_boxlevel( int on, MenuEnt *me, void *vblr ) {
   struct boxleveler *blr = (struct boxleveler *)vblr;
   struct stuff *st = blr->st;
   int bit = 1 << blr->level;
   char str[32];

   if(blr->level >= st->boxlevels) {
	menu_settitle(me, "" );
   } else {
	if(me->state == ME_PICKED)
	    st->boxlevelmask ^= bit;
	sprintf(str, "Level %d %s", blr->level, 
		st->boxlevelmask & bit ? "ON" : "off" );
	menu_settitle( me, str );
   }
}

void
set_point( int on, MenuEnt *me, void *st )
{
  struct stuff *stuff = (struct stuff *)st;
  stuff->usepoint = (on<0) ? !stuff->usepoint : on;
  menu_settitle( me, stuff->usepoint ? "Point On" : "Point Off" );
}

void
set_poly( int on, MenuEnt *me, void *st )
{
  struct stuff *stuff = (struct stuff *)st;
  stuff->usepoly = (on<0) ? !stuff->usepoly : on;
  menu_settitle( me, stuff->usepoly ? "Poly On" : "Poly Off" );
}

void
set_label( int on, MenuEnt *me, void *st )
{
  struct stuff *stuff = (struct stuff *)st;
  stuff->usetext = (on<0) ? !stuff->usetext : on;
  menu_settitle( me, stuff->usetext ? "Labels On" : "Labels Off" );
}

void
set_alpha( float alpha, MenuEnt *me, void *st )
{
   char str[20];
   ((struct stuff *)st)->alpha = alpha;
   sprintf(str, "Alpha %.2f", alpha);
   menu_settitle( me, str );
}

void
set_fog( float fog, MenuEnt *me, void *st )
{
   char str[20];
   ((struct stuff *)st)->fog = fog;
   sprintf(str, "Fog %.2f (not yet)", fog);
   menu_settitle( me, str );
}

void
set_polysize( float polysize, MenuEnt *me, void *st )
{
   char str[20];
   ((struct stuff *)st)->polysize = polysize;
   sprintf(str, "Spotsize %.2f", polysize);
   menu_settitle( me, str );
}
#endif /*CAVEMENU*/



int specks_set_byvariable( struct stuff *st, char *str, int *val )
{
  int i;
  char *ep;
  int best = -1;
  if(str == NULL) return 0;

  if(!strcasecmp( str, "const" ) || !strcasecmp( str, "constant" )
				 || !strcasecmp( str, "rgb" )) {
    *val = CONSTVAL;
    return -1;
  }
	
  for(i = 0; i < MAXVAL; i++) {
    if(strncasecmp( str, st->vdesc[st->curdata][i].name, strlen(str) ) == 0) {
	best = i;
	if(!strcmp( str, st->vdesc[st->curdata][i].name ))
	    break;
    }
  }
  if(best >= 0) {
    *val = best;
    return 1;
  }
  i = strtol(str, &ep, 0);
  if(ep == str || i < 0 || i > MAXVAL || (*ep != '\0' && *ep != '('))
    return 0;
  *val = i;
  return 1;
}

static char *putcoords( char *buf, Point *pos, Matrix *T,
		int isvec, char *cartfmt,
		char *lonlatfmt, char *hmdmfmt )
{
  Point p;
  char *cp = buf;
  float lat, lon, r;
  if(T == NULL) T = &Tidentity;
  buf[0] = '\0';
  if(isvec)
    vtfmvector( &p, pos, T );
  else vtfmpoint( &p, pos, T );
  if(cartfmt)
    cp += sprintf(cp, cartfmt, p.x[0],p.x[1],p.x[2]);
  lon = atan2(p.x[1], p.x[0]) * 180/M_PI;
  if(lon < 0) lon += 360;
  lat = atan2(p.x[2], hypot(p.x[1],p.x[0])) * 180/M_PI;
  r = vlength(&p);
  if(lonlatfmt)
    cp += sprintf(cp, lonlatfmt, lon, lat, r);
  if(hmdmfmt) {
    char *sign = (lat < 0) ? "-" : "+";
    lat = fabs(lat);
    lon /= 15;
    cp += sprintf(cp, hmdmfmt, (int)lon, 60. * (lon - (int)lon),
				sign, (int)lat, 60. * (lat - (int)lat),
				r);
  }
  return buf;
}

static char *whereis(struct stuff *st, char *buf, Point *pos, int isvec)
{
  putcoords(buf, pos, NULL, isvec, "%.2f %.2f %.2f", NULL, NULL);
  if(strstr(st->altcoord[0].name, "2000")
	|| strstr(st->altcoord[0].name, "1950")
	|| strstr(st->altcoord[0].name, "eq")) {

    sprintf(buf + strlen(buf), "; %.9s: ", st->altcoord[0].name);
    putcoords(buf+strlen(buf), pos, &st->altcoord[0].w2coord, isvec,
		NULL, NULL, "%02d:%02.0f %s%02d:%02.0f %g");

  } else if(st->altcoord[0].name[0] != '\0') {
    sprintf(buf + strlen(buf), "; %.9s: ", st->altcoord[0].name);
    putcoords(buf+strlen(buf), pos, &st->altcoord[0].w2coord, isvec,
		NULL, "%.2f %.2f %g", NULL);
  }
  return buf;
}


static Point zero = {0,0,0}, forward = {0,0,-1};

#if CAVE

void set_where(int which, MenuEnt *me, void *vst) {
  struct stuff *st = (struct stuff *)vst;
  char buf[180];
  Point pos;
  Matrix cam2w;
  switch(which) {
  case 0:
    CAVEGetPosition( CAVE_HEAD_NAV, pos.x );
    sprintf(buf, "head at ");
    whereis(st, buf+strlen(buf), &pos, 0);
    break;

  case 1:
    VD_get_cam2world_matrix( cam2w.m );
    vtfmpoint( &pos, &zero, &cam2w );
    sprintf(buf, "camera at ");
    whereis(st, buf+strlen(buf), &pos, 0);
    break;

  case 2:
    VD_get_cam2world_matrix( cam2w.m );
    vtfmvector( &pos, &forward, &cam2w );
    sprintf(buf, "cam looks ");
    whereis(st, buf+strlen(buf), &pos, 1);
    break;

  case 3:
    VD_get_interest( pos.x );
    sprintf(buf, "interest at ");
    whereis(st, buf+strlen(buf), &pos, 0);
    break;
  default:
    return;
  }
  menu_settitle( me, buf );
}
#endif /*CAVE*/

static void tellwhere(struct stuff *st)
{
    Point pos, fwd;
    Matrix cam2w;
    char buf[180];

#ifdef CAVE
    CAVENavConvertCAVEToWorld( zero.x, pos.x );
    msg("cave at %s", whereis(st, buf, &pos, 0));
    CAVEGetPosition( CAVE_WAND_NAV, pos.x );
    msg("wand at %s", whereis(st, buf, &pos, 0));
    CAVEGetVector( CAVE_WAND_FRONT_NAV, fwd.x );
    VD_get_cam2world_matrix( cam2w.m );
#else
    parti_getc2w( &cam2w );
#endif

    vtfmpoint( &pos, &zero, &cam2w );
    vtfmvector( &fwd, &forward, &cam2w );
    msg("camera at %s", whereis(st, buf, &pos, 0));
    msg("looking to %s", whereis(st, buf, &fwd, 1));
}

FILE *asyncmd[MAX_ASYNC];

int specks_add_async( struct stuff *st, char *cmdstr )
{
  int i;

#if unix  /* not WIN32 */
  if(cmdstr == NULL)
    return -1;
  for(i = 0; i < MAX_ASYNC; i++) {
    if(asyncmd[i] == NULL) {
	asyncmd[i] = popen(cmdstr, "r");
#if !CAVEMENU
	parti_asyncfd( fileno( asyncmd[i] ) );
#endif
	return i;
    }
  }
#endif /*unix*/
  msg("Sorry, all %d async-command slots full: can't run %s", MAX_ASYNC, cmdstr);
  return -1;
}

int specks_check_async( struct stuff **stp )
{
  int i, any = 0;
  char *av[128];
  int ac;
  char buf[10240], vdbuf[1024];
  static int reentered = 0;	/* Don't allow recursion! */

#if unix  /* not WIN32 */

  if(stp == NULL || *stp == NULL) return 0;
  if(reentered>0) return 0;
  reentered = 1;

  for(i = 0; i < MAX_ASYNC; i++) {
    int brackets = 0;
    int none = 1;
    int nlines = 0;

    while(asyncmd[i] && (none || brackets > 0)) {
	int c = (brackets>0) ? fnextc(asyncmd[i], 1)
			     : async_fnextc(asyncmd[i], 1);
	switch(c) {
	case EOF:
	    if(getenv("DBG")) msg("Closing async %d", i);
#if !CAVEMENU
	    parti_unasyncfd( fileno( asyncmd[i] ) );
#endif
	    pclose(asyncmd[i]);
	    asyncmd[i] = NULL;
	    break;
	case -2: /* NODATA */
	    none = 0;
	    break;
	default:
	    /* Got something.  Assume that we can read a whole line quickly. */
	    if(fgets(buf, sizeof(buf), asyncmd[i]) == NULL) {
		if(getenv("DBG")) msg("fgets: Closing async %d", i);
		pclose(asyncmd[i]);
		asyncmd[i] = NULL;
		break;
	    }
	    nlines++;
	    any++;
	    sprintf(vdbuf, "%.1023s", buf);

	    for(c = 0; isspace(buf[c]) || buf[c] == '{' || buf[c] == '}'; c++) {
		if(buf[c] == '{') brackets++;
		else if(buf[c] == '}' && brackets > 0) brackets--;
		buf[c] = ' ';
	    }
	    if(getenv("DBG")) msg("async %d[%d]{%d}: %s", i, nlines, brackets, buf);
	    av[0] = strtok(buf, separ);
	    for(ac = 1; ac < 127 && (av[ac] = strtok(NULL, separ)) != NULL; ac++)
		;
	    av[ac] = NULL;
	    if(av[0] != NULL) {
		none = 0;
#if CAVEMENU
		if(!specks_parse_args( stp, ac, av ))
		    VIDI_queue_commandstr( vdbuf );

#else /* non-virdir version */
		parti_redraw();
		specks_parse_args( stp, ac, av );
#endif
	    }

	}
    }
  }
  reentered = 0;
#endif /*unix not WIN32*/
  return any;
}

    
int getbool( char *str, int defval ) {
  int v;
  char *ep;
  if(str == NULL) return defval;

  if(!strcasecmp(str, "on")) return 1;
  if(!strcasecmp(str, "off")) return 0;
slevy's avatar
 
slevy committed
  if(!strcasecmp(str, "toggle")) return !defval;
teuben's avatar
teuben committed
  if(!strcasecmp(str, "all")) return -1;
  v = strtol(str, &ep, 0);
  if(str == ep) return defval;
  return v;
}

slevy's avatar
 
slevy committed
double getfloat( char *str, double defval ) {
  double v;
teuben's avatar
teuben committed
  char *ep;
  int prefix = 0;
  if(str == NULL) return defval;
  if(str[0] == '-' && (str[1] == '=' || str[1] == '-'))	/* -=, -- */
    prefix = *str++;
  if(str[0] == '*' || str[0] == '+' || str[0] == '/' || str[0] == 'x')
    prefix = *str++;
  if(str[0] == '=')
    str++;
  v = strtod(str, &ep);
  if(ep == str) {
    v = defval;
  } else {
    switch(prefix) {
    case '-': v = defval - v; break;
    case '+': v = defval + v; break;
    case '*': v = defval * v; break;
    case '/': v = (v != 0) ? defval / v : 0; break;
    }  /* default: just use v */
  }
  return v;
}


int
specks_parse_args( struct stuff **stp, int argc, char *argv[] )
{
  int i;
  struct stuff *st = *stp;

  while( argc>0 && (!strncmp( argv[0], "specks", 4 ) || !strcmp( argv[0], "feed" ) )) {
    argc--, argv++;
    /* VD_select_menu( specks_menuindex ); */
  }

  if(argc <= 0)
    return 0;

  if(!strcmp( argv[0], "?" ) || !strcmp( argv[0], "help" )) {
    static char *help1[] = {
"specks commands:",
" speed   data-steps per VirDir second",
" step N  -or-  step +N  -or-  step -N  Go to data step N, or step fwd/back",
slevy's avatar
 
slevy committed
" trange on|off|MIN MAX [WRAP]	limit range of datastep times",
teuben's avatar
teuben committed
" run				toggle auto-play (run/stop)",
" color VARNO-or-NAME		color particles by VARNO'th variable (0..%d)",
" color const R G B		set all particles to be that color",
" lum   VARNO-or-NAME		tie particle size/luminosity to VARNOth var",
" lum   const LUM		set all particles to be that brightness",
" slum  SCALEFACTOR		scale particle size by SCALEFACTOR",
" psize SIZE			scale particle size by SIZE * SCALEFACTOR",
slevy's avatar
 
slevy committed
" depthsort			sort polygons by depth",
teuben's avatar
teuben committed
" see   DATASETNO-or-NAME	show that dataset (e.g. \"see 0\" or \"see gas\")",
" read  [-t time] DATAFILENAME	read data file (e.g. to add new specks)",
" ieee  [-t time] IEEEIOFILE	read IEEEIO file (starting at given timestep)",
" sdb   [-t time] SDBFILE	read .sdb star-data file",
" annot [-t time] string	set annotation string (for given timestep)",
slevy's avatar
 
slevy committed
" add  DATAFILECOMMAND		enter a single datafile command (ditto)",
teuben's avatar
teuben committed
" every N			subsample: show every Nth particle",
" bound				show bounds (coordinate range of all particles)",
" clipbox {on | off | X0,X1 Y0,Y1 Z0,Z1 | CENX,Y,Z RADX,Y,Z | X0 Y0 Z0  X1 Y1 Z1} clipping region",
" add box [-n boxno] [-l level] CENX,Y,Z RX,RY,RZ | X0 Y0 Z0 X1 Y1 Z1  marker-box",
" boxlabels                     show box numbers",
" boxaxes                       show R/G/B axes from X0,Y0,Z0 box corner"
#if CAVEMENU
" boxes {off|on|only}		hide/show all AMR boxes",
" {hide|show} LEVELNO ...	hide/show AMR boxes of those levels (or \"all\")",
" {point|polygon|texture} {on|off}",
" fmenu HEIGHT  -or-  fmenu XPOS YPOS  -or- fmenu wall WALLNO",
#else
" readpath  FILENAME.wf		read Wavefront camera path (from virdir \"wfout\")",
" play  SPEED[f]		play path (at SPEED times normal speed)",
"			(with \"f\" suffix, play every SPEEDth frame)",
" frame FRAMENO			go to Nth frame",
" focal FOCALLEN		focal length (determines fly/tran speed)",
" clip  NEAR FAR		clipping distances",
" interest X Y Z		center of rotation for orbit/rotate",
" snapset  filestem [frameno]	set snapshot parameters",
" snapshot [frameno]		take snapshot [uses convert(1)]",
slevy's avatar
 
slevy committed
" kira {node|ring|size|scale|span|track}  starlab controls; try \"kira ?\"",
teuben's avatar
teuben committed
#endif
    };

    for(i = 0; i < COUNT(help1); i++)
	msg(help1[i], MAXVAL-1);
  

  } else if(!strcmp( argv[0], "read" )) {
	if(argc > 1)
	    specks_read( &st, argv[1] );

  } else if(!strcmp( argv[0], "include" )) {
#ifdef NOTYET
#endif

  } else if( !strcmp(argv[0], "on") || !strcmp(argv[0], "off")
	    || !strcmp(argv[0], "enable") || !strcmp(argv[0], "disable") ) {

	st->useme = argc>1 ? getbool(argv[1], st->useme) : (argv[0][1]=='n');
	msg(st->useme ? "enabled" : "disabled");
	
  } else if(!strcmp( argv[0], "eval" ) || !strcmp(argv[0], "add")) {
	int k, io[2];
	FILE *tf;
	char fdname[64+L_tmpnam];

#ifdef WIN32
	tmpnam(fdname);
	tf = fopen(fdname, "w");
#else /* unix */
	pipe(io);
	sprintf(fdname, "/dev/fd/%d", io[0]);
	tf = fdopen(io[1], "w");
#endif
	if(tf == NULL) {
	    fprintf(stderr, "Yeow: can't make temp file?\n");
	} else {
	    for(k = 1; k < argc; k++)
		fprintf(tf, "%s ", argv[k]);
	    fprintf(tf, "\n");
	    fclose(tf);
	    specks_read( &st, fdname );
#ifdef WIN32
	    unlink(fdname);
#endif
	}

#ifndef WIN32
	close(io[0]);
#endif

  } else if(!strcmp( argv[0], "async" )) {
	char tbuf[5120];
	tbuf[0] = '\0';
	for(i = 1; i < argc; i++)
	    sprintf(tbuf+strlen(tbuf), " %s", argv[i]);
	specks_add_async( st, tbuf );

  } else if(!strcmp( argv[0], "update" )) {
	parti_update();

  } else if(!strcmp( argv[0], "hist" )) {
	register struct specklist *sl;
	register struct speck *sp;
	Point xmin,xmax, mean, mid, radius;
	int nspecks, nclipped, nthreshed, nlow, nhigh, nundefined;
	int clipping = (st->clipbox.level > 0);
	int threshing = st->usethresh & P_USETHRESH;
	int nbuckets = 11;
	int dolog = 0;
	int *bucket;
	float v, vmin, vmax, vrange;
	int histvar;
	int i, k, bno;
	struct valdesc *vd;

	for(i=1,k=1; i+1 < argc; k++) {
	    int yes = argv[i][0] == '-';
	    if(argv[i][0] != '-' && argv[i][1] != '+') break;
	    switch(argv[i][k]) {
		case 't': threshing = yes ? THRESHBIT : 0; break;
		case 'c':
		case 'b': clipping = yes; break;
		case 'n':
		    sscanf(argv[i][++k] ? &argv[i][k] : argv[++i],
			"%d", &nbuckets);
		    i++; k=0;
		    break;
		case 'l': dolog = yes; break;
		case '\0': i++; k=0;
	    }
	}

	if(!specks_set_byvariable( st, argv[i], &histvar )) {
	    msg("hist %s: expected name/index of data variable", argv[i]);
	    return 1;
	}

	vd = &st->vdesc[st->curdata][histvar];
	vmin = vd->min;
	vmax = vd->max;

	if(i+1<argc) sscanf(argv[i+1], "%f", &vmin);
	if(i+2<argc) sscanf(argv[i+2], "%f", &vmax);

	if(vmax < vmin)
	    v = vmax, vmax = vmin, vmin = v;

	if(dolog && (vmin <= 0 || vmax <= 0)) {
	    msg("hist: can't take logs (-l) if range includes zero!");
	    dolog = 0;
	}
	if(dolog) {
	    vmin = log(vmin);
	    vmax = log(vmax);
	}
	vrange = (nbuckets-1) / ((vmax>vmin) ? vmax - vmin : 1);

	if(nbuckets<=0 || nbuckets>20000) {
	    msg("hist -n %d: Incredible number of histogram buckets", nbuckets);
	    return -1;
	}

	bucket = NewA(int, nbuckets);
	memset(bucket, 0, nbuckets*sizeof(int));

	nspecks = nclipped = nthreshed = nundefined = nlow = nhigh = 0;
	for(sl = st->sl; sl != NULL; sl = sl->next) {
	    if((sp = sl->specks) == NULL || sl->text != NULL)
		continue;
	    nspecks += sl->nspecks;
	    if(sl->bytesperspeck < SMALLSPECKSIZE(histvar+1)) {
		nundefined += sl->nspecks;
		continue;
	    }
	    for(i = sl->nspecks; --i >= 0; sp = NextSpeck( sp, sl, 1 )) {
		if(clipping &&
		  (sp->p.x[0] < st->clipbox.p0.x[0] ||
		   sp->p.x[0] > st->clipbox.p1.x[0] ||
		   sp->p.x[1] < st->clipbox.p0.x[1] ||
		   sp->p.x[1] > st->clipbox.p1.x[1] ||
		   sp->p.x[2] < st->clipbox.p0.x[2] ||
		   sp->p.x[2] > st->clipbox.p1.x[2])) {
			nclipped++;
			continue;
		}
		if(threshing & sp->rgba) {
			nthreshed++;
			continue;
		}
		v = sp->val[histvar];
		if(dolog) {
		    if(v <= 0) {
			nundefined++;
			continue;
		    }
		    v = log(v);
		}
		bno = (int) ((v - vmin)*vrange);
		if(bno < 0) nlow++;
		else if(bno >= nbuckets) nhigh++;
		else bucket[bno]++;
	    }
	}
	if(nspecks == 0) {
	    msg("No specks loaded yet");
	} else {
	    msg("hist -n %d %s%s%s%d(%s) %g %g => ",
		nbuckets, dolog?"-l ":"", clipping?"-c ":"", threshing?"-t ":"",
		histvar, vd->name,
		dolog ? exp(vmin) : vmin,
		dolog ? exp(vmax) : vmax);
	    msg("Total %d, %d < min, %d > max, %d undefined, %d clipped, %d threshed",
		nspecks, nlow, nhigh, nundefined, nclipped, nthreshed);
	    k = nlow;
	    msg("%d\t< %g", nlow, dolog ? exp(vmin) : vmin);
	    for(i = 0; i < nbuckets; i++) {
		v = vmin + ( (vrange>0) ? i / vrange : 0 );
		msg("%d\t>= %g", bucket[i], dolog ? exp(v) : v);
	    }
	    msg("%d\t> %g", nhigh, dolog ? exp(vmax) : vmax);
	}


  } else if(!strcmp( argv[0], "bound" )) {
	register struct specklist *sl;
	register struct speck *sp;
	Point xmin,xmax, mean, mid, radius;
	int minmaxk = MAXVAL+1, maxmaxk = 0;
	int nspecks = 0;

	xmin.x[0] = xmin.x[1] = xmin.x[2] = 1e38;
	xmax.x[0] = xmax.x[1] = xmax.x[2] = -1e38;
	mean.x[0] = mean.x[1] = mean.x[2] = 0;
	for(sl = st->sl; sl != NULL; sl = sl->next) {
	    int i, maxk;
	    if((sp = sl->specks) == NULL)
		continue;
	    for(maxk=0; maxk<MAXVAL && SMALLSPECKSIZE(maxk)<sl->bytesperspeck; maxk++)
		;
	    if(minmaxk > maxk) minmaxk = maxk;
	    if(maxmaxk < maxk) maxmaxk = maxk;
	    for(i = sl->nspecks; --i >= 0; sp = NextSpeck( sp, sl, 1 )) {
		if(xmin.x[0] > sp->p.x[0]) xmin.x[0] = sp->p.x[0];
		if(xmax.x[0] < sp->p.x[0]) xmax.x[0] = sp->p.x[0];
		mean.x[0] += sp->p.x[0];
		if(xmin.x[1] > sp->p.x[1]) xmin.x[1] = sp->p.x[1];
		if(xmax.x[1] < sp->p.x[1]) xmax.x[1] = sp->p.x[1];
		mean.x[1] += sp->p.x[1];
		if(xmin.x[2] > sp->p.x[2]) xmin.x[2] = sp->p.x[2];
		if(xmax.x[2] < sp->p.x[2]) xmax.x[2] = sp->p.x[2];
		mean.x[2] += sp->p.x[2];
	    }
	    nspecks += sl->nspecks;
	}
	if(nspecks == 0) {
	    msg("No specks loaded yet");
	} else {
	
	    vscale( &mean, 1.0/nspecks, &mean );
	    vcomb( &mid, .5,&xmin, .5,&xmax );
	    vcomb( &radius, .5,&xmax, -.5,&xmin );
	    msg( "%d specks in range %g %g %g .. %g %g %g",
		nspecks, xmin.x[0],xmin.x[1],xmin.x[2],
		xmax.x[0],xmax.x[1],xmax.x[2]);
	    msg( "midbbox %g %g %g  boxradius %g %g %g",
		mid.x[0],mid.x[1],mid.x[2],
		radius.x[0],radius.x[1],radius.x[2]);
	    msg( "mean %g %g %g", mean.x[0],mean.x[1],mean.x[2] );
	}

  } else if(!strcmp( argv[0], "fspeed" )) {
	if(argc>1) {
teuben's avatar
teuben committed
	    specks_set_fspeed( st, getfloat(argv[1], st->fspeed) );
teuben's avatar
teuben committed
	    st->playnext = 0.0;
	}
	msg("fspeed %g steps per real-time second", st->fspeed);

  } else if(!strcmp( argv[0], "speed" )) {
	if(argc>1)
slevy's avatar
 
slevy committed
	    specks_set_speed( st, getfloat(argv[1], clock_speed(st->clk)) );
	msg("speed %g steps per anim second", clock_speed(st->clk));
teuben's avatar
teuben committed
	
  } else if(!strcmp( argv[0], "run" )) {
slevy's avatar
 
slevy committed
	parti_set_running( st, getbool( argv[1], 1 ) );
teuben's avatar
teuben committed
	st->playnext = 0.0;

slevy's avatar
 
slevy committed
  } else if(!strcmp( argv[0], "depthsort" )) {
	if(argc > 1) st->depthsort = getbool(argv[1], st->depthsort);
	msg("depthsort %s", st->depthsort ? "on" : "off" );

teuben's avatar
teuben committed
  } else if(!strcmp( argv[0], "fade" )) {
	char *fmt = "fade what?";
	if(argc>1) {
	    if(!strncmp(argv[1],"sph",3) || !strncmp(argv[1],"rad",3))
		st->fade = F_SPHERICAL;
	    else if(!strncmp(argv[1],"pla",3))
		st->fade = F_PLANAR;
	    else if(!strncmp(argv[1],"con",3) || !strncmp(argv[1],"ort",3))
		st->fade = F_CONSTANT;
	    else if(!strncmp(argv[1],"knee",3))
		st->fade = F_KNEE12;
	    else if(!strncmp(argv[1],"lin",3))
		st->fade = F_LINEAR;
	    else {
		msg("fade {sph|planar|const|linear|knees}");
		return -1;
	    }
	}
	if(argc>2) sscanf(argv[2], "%f", &st->fadeknee2);
	if(argc>3) sscanf(argv[3], "%f", &st->knee2steep);
	if(argc>4) sscanf(argv[4], "%f", &st->fadeknee1);
	if(argc>5) sscanf(argv[5], "%f%*c%f%*c%f",
			&st->fadecen.x[0], &st->fadecen.x[1], &st->fadecen.x[2]);
	if(argc>6) sscanf(argv[6], "%f", &st->fadecen.x[1]);
	if(argc>7) sscanf(argv[7], "%f", &st->fadecen.x[2]);
	switch(st->fade) {
	case F_SPHERICAL: fmt = "fade spherical  (1/r^2 from eyepoint)"; break;
	case F_PLANAR:    fmt = "fade planar     (1/r^2 from eye plane)"; break;
	case F_CONSTANT:  fmt = "fade const %g   (as if seen at given dist)"; break;
	case F_LINEAR:    fmt = "fade linear %g  (1/r, scaled to match planar at dist)"; break;
	case F_KNEE12:	  fmt = "fade knees %g %g  %g (fardist, steepness, neardist)"; break;
	case F_LREGION:	  fmt = "fade lregion %g  %g %g  %g %g %g (refdist; steepness, Rregion)"; break;
	}
	msg(fmt, st->fadeknee2, st->knee2steep, st->fadeknee1,
		st->fadecen.x[0], st->fadecen.x[1], st->fadecen.x[2]);

  } else if(!strcmp( argv[0], "clipbox" ) || !strcmp( argv[0], "cb" )) {
	Point cen, rad;
	int k;
	switch(argc) {
	case 2:	st->clipbox.level = getbool(argv[1], st->clipbox.level);
		break;
	case 3: if(3==sscanf(argv[1], "%f%*c%f%*c%f",
				&cen.x[0],&cen.x[1],&cen.x[2])
			&& 0 < (k = sscanf(argv[2], "%f%*c%f%*c%f",
				&rad.x[0],&rad.x[1],&rad.x[2]))) {
		    if(k==1) rad.x[1] = rad.x[2] = rad.x[0];
		    vsub( &st->clipbox.p0, &cen, &rad );
		    vadd( &st->clipbox.p1, &cen, &rad );
		    st->clipbox.level = 1;	/* activate */
		} else {
		    msg("clipbox: xmin,xmax ymin,ymax zmin,zmax  or cenx,y,z radiusx,y,z");
		}
		break;
	case 4:
		if(2 == sscanf(argv[1], "%f%*c%f", &cen.x[0], &rad.x[0]) &&
		   2 == sscanf(argv[2], "%f%*c%f", &cen.x[1], &rad.x[1]) &&
		   2 == sscanf(argv[3], "%f%*c%f", &cen.x[2], &rad.x[2])) {
		    st->clipbox.p0 = cen;
		    st->clipbox.p1 = rad;
		    st->clipbox.level = 1;	/* activate */
		} else {
		    msg("clipbox: xmin,xmax ymin,ymax zmin,zmax  or cenx,y,z radiusx,y,z");
		}
		break;

	case 7: for(k = 0; k < 3; k++) {
		    if(sscanf(argv[k+1], "%f", &st->clipbox.p0.x[k]) <= 0
			|| sscanf(argv[k+4], "%f", &st->clipbox.p1.x[k]) <= 0)
			break;
		}
		if(k == 3) {
		    st->clipbox.level = 1;
		} else {
		    msg("clipbox: xmin ymin zmin  xmax ymax zmax");
		}
		break;
	}
	vcomb( &cen, .5, &st->clipbox.p0, .5, &st->clipbox.p1 );
	vcomb( &rad, -.5, &st->clipbox.p0, .5, &st->clipbox.p1 );
	msg("clipbox %s  (%g,%g  %g,%g  %g,%g  or  %g,%g,%g %g,%g,%g)",
		st->clipbox.level ? "on":"off",
		st->clipbox.p0.x[0], st->clipbox.p1.x[0],
		st->clipbox.p0.x[1], st->clipbox.p1.x[1],
		st->clipbox.p0.x[2], st->clipbox.p1.x[2],
		cen.x[0],cen.x[1],cen.x[2],  rad.x[0],rad.x[1],rad.x[2]);

  } else if(!strcmp( argv[0], "object" ) || sscanf( argv[0], "g%d", &i ) > 0) {
	struct stuff *tst = st;
	i = parti_object( argv[ argv[0][0]=='g' ? 0 : 1 ], &tst );
	if(i>=0 && argv[0][0] == 'g' && argc > 1) {
	    specks_parse_args( &tst, argc-1, argv+1 );
	} else {
	    st = tst;
	    msg("object g%d selected (%d particles)", i,
		specks_count( st->sl ));
	}

slevy's avatar
 
slevy committed
  } else if(!strcmp( argv[0], "gall" ) || !strncmp( argv[0], "allobj", 6 )) {
	parti_allobjs( argc-1, argv+1 );
teuben's avatar
teuben committed

  } else if(!strcmp(argv[0], "tfm")) {
	int inv = 0;
	int mulWorldside = 0, mulObjside = 0;
	int hpr = 0;
	int calc = 0;
	int any, more, a0;
	float scl;
	Matrix ot, t;
	Point xyz;
	float aer[3];
	char *key;

	i = 1;
	key = argv[i];
	if(key == NULL) key = "";
	for(more = 1; *key != '\0' && more; key++) {
	  switch(*key) {
	  case '=': break;
	  case '*': mulWorldside = 1; break;
	  case '/': inv = 1; break;
	  case 'h': case 'p': case 'r': hpr = 1; break;
	  case ' ': case '\t': break;
	  case '\0':
		if(i < argc-1) {
		    key = argv[++i];
		    break;
		}
		/* else fall into default */
	  default: more = 0; key--; break;
	  }
	}

	parti_geto2w( st, parti_object( NULL, &st ), &ot );

	
	a0 = i;
	argv[a0] = key;
	for(any = 0; any < 16 && a0+any<argc
			&& sscanf(argv[a0+any], "%f", &t.m[any]) > 0; any++)
		;
	switch(any) {
	case 1:
		scl = t.m[0];
		t = Tidentity;
		t.m[0*4+0] = t.m[1*4+1] = t.m[2*4+2] = scl;
		break;
	case 6:
	case 7:		/* ignore fovy if included */
		xyz = *(Point *)&t.m[0];
		if(hpr) {
		    aer[0] = t.m[3], aer[1] = t.m[4], aer[2] = t.m[5];
		} else {
		    /* Note we permute: px py pz rx ry rz == px py pz e a r */
		    aer[1] = t.m[3], aer[0] = t.m[4], aer[2] = t.m[5];
		}
		xyzaer2tfm( &t, &xyz, aer );
		break;

	case 16: break;
	case 0:  t = ot; break;
	default:
	    msg("Usage: tfm [*] [/] [\"hpr\"] [16 numbers or 6 numbers] [=]");
	    return -1;
	}

	for(i = a0+any-1; i < argc; i++) {
	    if(strchr(argv[i], '=')) calc = 1;
	    if(strchr(argv[i], '*')) mulObjside = 1;
	}

	if(any) {
	    if(inv)
		eucinv( &t, &t );
	    if(mulWorldside)
		mmmul( &t, &ot, &t );
	    if(mulObjside)
		mmmul( &t, &t, &ot );

	    /* with trailing '=', just print result without assignment */

	    if(!calc)
		parti_seto2w( st, parti_object( NULL, &st ), &t );
	}

	tfm2xyzaer( &xyz, aer, &t );
	msg("obj2w:  x y z %s   %g %g %g  %g %g %g",  hpr?"h p r":"rx ry rz",
		xyz.x[0],xyz.x[1],xyz.x[2],  aer[1-hpr],aer[hpr],aer[2] );
	for(i = 0; i < 3; i++)
	    msg(" %10.7f %10.7f %10.7f %10.7f", t.m[i*4+0],t.m[i*4+1],t.m[i*4+2],t.m[i*4+3]);
	msg(" %10.7g %10.7g %10.7g %10.7g", t.m[3*4+0],t.m[3*4+1],t.m[3*4+2],t.m[3*4+3]);
	eucinv( &t, &t );
	tfm2xyzaer( &xyz, aer, &t );
	msg("w2obj:  x y z %s   %g %g %g  %g %g %g",  hpr?"h p r":"rx ry rz",
		xyz.x[0],xyz.x[1],xyz.x[2],  aer[1-hpr],aer[hpr],aer[2] );
	for(i = 0; i < 3; i++)
	    msg(" %10.7f %10.7f %10.7f %10.7f", t.m[i*4+0],t.m[i*4+1],t.m[i*4+2],t.m[i*4+3]);
	msg(" %10.7g %10.7g %10.7g %10.7g", t.m[3*4+0],t.m[3*4+1],t.m[3*4+2],t.m[3*4+3]);

  } else if(!strcmp( argv[0], "bgcolor" )) {
	char *result, given[64];
	if(argc == 4) {
	    sprintf(given, "%.10s,%.10s,%.10s", argv[1],argv[2],argv[3]);
	    result = parti_bgcolor( given );
	} else {
	    result = parti_bgcolor( argv[1] );
	}
	msg("bgcolor %s", result);

	    
#if !CAVEMENU
	/* virdir emulation */

  } else if(!strcmp( argv[0], "stereo" )) {
	for(i = 1; i < argc; i++)
	    parti_stereo( argv[i] );

	msg("stereo %s", parti_stereo(NULL));

  } else if(!strncmp( argv[0], "snapset", 7 ) || !strcmp( argv[0], "snapshot" )) {
	char *frameno = NULL, *basename = NULL;
	char *tail = NULL;
	if(argc > 2) frameno = argv[2];
	if(argc == 2) {
	    strtol(argv[1], &tail, 0);
	    if(tail != argv[1] && *tail == '\0')
		frameno = argv[1];
	}
	if(argc > 1 && frameno == NULL)
	    basename = argv[1];
	parti_snapset( basename, frameno );
	if(0!=strncmp( argv[0], "snapset", 7 )) {
	    char snapinfo[1024]; 
	    if(parti_snapshot(snapinfo) >= 0)
		msg("Snapped %s", snapinfo);
	}

  } else if(!strcmp( argv[0], "move" ) || !strcmp( argv[0], "move-objects" )) {
	i = parti_move( argv[1], &st );
	msg(i>=0 ? "move-objects on ; g%d selected" : "move-objects off", i);
	    
  } else if(!strcmp( argv[0], "clip" )) {
	msg("%s", parti_clip( argv[1], argc>2?argv[2]:NULL ) );

  } else if(!strcmp( argv[0], "ortho" )) {
#ifdef NOTYET
	int ortho = parti_ortho( argc>1 ? argv[1] : NULL);
	float fov = parti_fov(NULL);
	msg(ortho ? "ortho on (fov %g units)" : "ortho off (fov %g degrees)"
#endif

  } 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(!strncmp( argv[0], "focal", 5 )) {
	float focallen = parti_focal( (argc>1) ? argv[1] : NULL );
	msg("focallength %g", focallen);

  } else if(!strncmp( argv[0], "jump", 4 )) {
	Point xyz;
	float aer[3];
	static int stupid[3] = {1,0,2};	/* aer -> rx ry rz */
	Matrix c2w;
	parti_getc2w( &c2w );
	tfm2xyzaer( &xyz, aer, &c2w );
	if(argc>1) {
	    for(i=1; i<argc && i<4+3; i++) {
		if(i-1<3) xyz.x[i-1] = getfloat(argv[i], xyz.x[i-1]);
		else aer[stupid[i-4]] = getfloat(argv[i], aer[stupid[i-4]]);
	    }
	    xyzaer2tfm( &c2w, &xyz, aer );
	    parti_setc2w( &c2w );
	}
	msg("jump %g %g %g  %g %g %g  (XYZ RxRyRz)",
		xyz.x[0],xyz.x[1],xyz.x[2],
		aer[1],aer[0],aer[2]);

  } else if((!strcmp( argv[0], "rdata" ) || !strcmp(argv[0], "readpath"))
		&& argc>1) {
	char *tfname = argv[1];
	char *realfile = findfile( NULL, tfname );
	if(realfile == NULL) {
	    tfname = (char *)alloca(strlen(argv[1]) + 32);
	    sprintf(tfname, "data/record/%s%s", argv[1],
		strstr(argv[1], ".wf") ? "" : ".wf");
	    realfile = findfile( NULL, tfname+12 );
	}
	if(realfile == NULL)
	    realfile = findfile( NULL, tfname );
	if(realfile)
	    parti_readpath( st, realfile );
	else
	    msg("%s: can't find \"%s\" nor data/record/... nor ...wf",
		argv[0],argv[1]);

  } else if(!strcmp( argv[0], "play" )) {
	parti_play( st, argc>1 ? argv[1] : NULL );

  } else if(!strcmp( argv[0], "frame" )) {
	struct wfpath *pp;
	i = parti_frame( st, argv[1], &pp );
	msg("frame %d (of %d..%d)", pp->curframe,
		pp->frame0, pp->nframes + pp->frame0 - 1);

  } else if(!strcmp( argv[0], "interest") || !strcmp( argv[0], "int" ) ||
		!strcmp( argv[0], "center" ) ||
		!strcmp( argv[0], "cen" )) {
	Point cen;
	float censize = parti_getcensize();
	parti_getcenter( &cen );
	if(argc > 1) {
	    sscanf(argv[1], "%f%*c%f%*c%f%*c%f",
		&cen.x[0],&cen.x[1],&cen.x[2], &censize);
	    if(argc > 3)
		for(i=0;i<3;i++)
		    cen.x[i] = getfloat(argv[i+1], cen.x[i]);
	    if(argc == 3 || argc == 5)
		sscanf(argv[argc-1], "%f", &censize);
	    parti_center( &cen );
	    if(censize != parti_getcensize())
		parti_censize( censize );
	}
#if CAVEMENU
	msg("center %g %g %g", cen.x[0],cen.x[1],cen.x[2]);
#else
	msg("center %g %g %g  %g(radius)", cen.x[0],cen.x[1],cen.x[2], censize);
#endif
  } else if(!strcmp( argv[0], "censize" )) {
	if(argc>1)
	    parti_censize( getfloat(argv[1], parti_getcensize()));
	msg("censize %g  (interest-marker size)", parti_getcensize());

#endif /*non-CAVEMENU*/

  } else if(!strcmp( argv[0], "step" )) {
slevy's avatar
 
slevy committed
	if(argc>1) {
	    if(argv[1][0] == '+') {
		if(argv[1][1] != '\0')
		    clock_set_step( st->clk, getfloat(&argv[1][1], 0) );
		clock_step( st->clk, 1 );
	    } else if(!strcmp(argv[1], "-")) {
		clock_step( st->clk, -1 );
	    } else {
		clock_set_time( st->clk, getfloat(argv[1], 0) );
	    }
teuben's avatar
teuben committed
	}
slevy's avatar
 
slevy committed

teuben's avatar
teuben committed

#if !CAVEMENU
slevy's avatar
 
slevy committed
	parti_set_running( st, 0 );
	specks_set_timestep(st);
teuben's avatar
teuben committed
#endif
teuben's avatar
teuben committed
	msg("step %g", st->currealtime);
teuben's avatar
teuben committed

slevy's avatar
 
slevy committed
  } else if(!strcmp( argv[0], "trange" ) || !strcmp( argv[0], "steprange" )) {
	double utmin = st->utmin;
	double utmax = st->utmax;
	double utwrap = st->utwrap;
	if(argc>1) {
	    if(!strcmp(argv[1],"on")) st->usertrange = 1;
	    else if(!strcmp(argv[1],"off")) st->usertrange = 0;
	    else {
		st->utmin = getfloat( argv[1], utmin );
		if(argc>2) st->utmax = getfloat( argv[2], utmax );
		if(argc>3) st->utwrap = getfloat( argv[3], utwrap );
		if(utmin != st->utmin || utmax != st->utmax)
		    st->usertrange = 1;
	    }
	}
	msg(st->usertrange ?
	    "trange %lg %lg  %lg (tmin tmax twrap)"
	  : "trange off   (%lg %lg  %lg  tmin tmax twrap)",
		st->utmin, st->utmax, st->utwrap);
slevy's avatar
 
slevy committed

teuben's avatar
teuben committed
  } else if(!strcmp( argv[0], "fwd" )) {
slevy's avatar
 
slevy committed
	if(argc>1)
	    clock_set_fwd( st->clk, getbool(argv[1], 1) );
	IFMENU( set_fwd( clock_fwd(st->clk), ment.fwd, st ) );
teuben's avatar
teuben committed

  } else if(!strcmp( argv[0], "gscale" )) {
	if(argc>1) sscanf(argv[1], "%f", &st->gscale);
	msg("gscale %g (scaling particles), then translating by %g %g %g",
		st->gscale, st->gtrans.x[0],st->gtrans.x[1],st->gtrans.x[2]);

  } else if(!strcmp( argv[0], "clearobj" )) {
	struct specklist **slp;
	slp = specks_timespecksptr( st, st->curdata, st->curtime );
	specks_discard( st, slp );
	st->sl = *slp;

  } else if(!strcmp( argv[0], "every" )) {
	if(argc>1) sscanf(argv[1], "%d", &st->subsample);
	if(st->subsample <= 0) st->subsample = 1;
	msg("display every %dth particle (of %d)",
			st->subsample, specks_count(st->sl));

slevy's avatar
 
slevy committed
  } else if(!strncmp( argv[0], "everycomp", 9 )) {
	st->everycomp = getbool(argv[1], st->everycomp);
	msg("everycomp %d (%s compensate for \"every\" subsampling)",
		st->everycomp,
		st->everycomp ? "do" : "don't");

teuben's avatar
teuben committed
  } else if(!strncmp( argv[0], "color", 5 )) {
	struct valdesc *vd;
	if(argc>1) {
	    if(!specks_set_byvariable( st, argv[1], &st->coloredby ))
		st->coloredby = CONSTVAL;
	}
	vd = &st->vdesc[st->curdata][st->coloredby];
	if(argc>2 && (!strncmp(argv[2], "int", 3) ||
		      !strncmp(argv[2], "exact", 5))) {
	    vd->call = 0;
	    vd->cexact = 1;
	    vd->cmin = 0;
	    argc--, argv++;
	}
	if(argc>2 && (!strncmp(argv[2], "cont", 4) || !strcmp(argv[2], "-exact"))) {
	   vd->cexact = 0;
	   argc--, argv++;
	}
	if(argc>2 && sscanf(argv[2], "%f", &vd->cmin))
	    vd->call = 0;
	if(argc>3 && sscanf(argv[3], "%f", &vd->cmax))
	    vd->call = 0;
	if((vd->cmin == vd->cmax && vd->cmin == 0) ||
	   (argc>2 && (!strcmp(argv[2],"-") || !strcmp(argv[2],"all")))) {

	    vd->cmin = vd->min, vd->cmax = vd->max, vd->call = 1;
	}
	if(argc>4 && st->coloredby == CONSTVAL) sscanf(argv[4], "%f", &vd->mean);
	if(st->coloredby == CONSTVAL) {
	    msg("coloring-by rgb %.3f %.3f %.3f",
		vd->cmin, vd->cmax, vd->mean);
	} else if(vd->cexact) {
	    msg("coloring-by %d(%s) exactly (cindex=data+%g; data %g..%g, cmap 0..%d)",
		st->coloredby, vd->name, vd->cmin,
		vd->min,vd->max, st->ncmap-1);
	} else {
	    if(vd->min == vd->cmin && vd->max == vd->cmax || vd->nsamples==0) {
		msg("coloring-by %d(%s) %g.. %g mean %.3g]",
		    st->coloredby, vd->name,
		    vd->cmin, vd->cmax, vd->mean);
	    } else {
		msg("coloring-by %d(%s) %g.. %g (data %g..%g mean %.3g)",
		    st->coloredby, vd->name,
		    vd->cmin, vd->cmax, vd->min, vd->max, vd->mean);
	    }
	}
	if(argc>1)
	    st->colorseq++;

  } else if(!strcmp( argv[0], "datavar" ) || !strcmp( argv[0], "dv" )) {
	int min = 0, max = CONSTVAL-1;
	if(argc>1 && specks_set_byvariable( st, argv[1], &min ))
	    max = min;
	if(st->ndata>1)
	    msg("In dataset %d %s:", st->curdata, st->dataname[st->curdata]);
	for(i = min; i <= max; i++) {
	    struct valdesc *vd = &st->vdesc[st->curdata][i];
	    if(max>min && vd->name[0] == '\0' && vd->max == vd->min)
		continue;
	    msg("datavar %d %s  %g.. %g mean %g%s%s",
		i, vd->name[0]=='\0'?"\"\"" : vd->name,
		vd->min, vd->max, vd->mean,
		st->sizedby==i ? "[lum]" : "",
		st->coloredby==i ? "[color]" : "");
	}

  } else if(!strcmp( argv[0], "datawait" )) {
	if(argc>1) {
	    switch(i = getbool(argv[1],-1)) {
	    case 0:
	    case 1:
		st->datasync = i;
		break;
	    }
	    msg("datawait %s", st->datasync ? "on" : "off");
	}
	specks_datawait(st);

slevy's avatar
 
slevy committed
  } else if(!strncmp(argv[0], "kira", 4)) {
slevy's avatar
 
slevy committed
	char *swhat = argv[1];
	char *sval = argc>2 ? argv[2] : NULL;
	int what;
	double val;
#ifndef USE_KIRA
	msg("Need to recompile with -DUSE_KIRA");
#else
	if(swhat == NULL) swhat = "?";

	if(!strncmp(swhat, "sep", 3) || !strncmp(swhat, "semi", 4)) {
	    kira_set( st, KIRA_RINGSIZE, swhat[2]=='p' ? KIRA_RINGSEP : KIRA_RINGA );
	    msg("kiractl ringsize %s",
		kira_get( st, KIRA_RINGSIZE ) == KIRA_RINGSEP ? "separation" : "semimajor");

	} else if(!strcmp(swhat, "ringsize") || !strcmp(swhat, "size")) {
	    if(sval) {
		val = !strncmp(sval,"sep",3) ? KIRA_RINGSEP
		    : !strncmp(sval,"semi",4) ? KIRA_RINGA
		    : !strcmp(sval,"a") ? KIRA_RINGA
		    : kira_get( st, KIRA_RINGSIZE );
		kira_set( st, KIRA_RINGSIZE, val );
		if(argc > 3)
		    kira_set( st, KIRA_RINGSCALE,
			getfloat( argv[3], kira_get( st, KIRA_RINGSCALE ) ) );
	    }
	    msg("kiractl ringsize %s %g",
		kira_get( st, KIRA_RINGSIZE ) == KIRA_RINGSEP ? "separation" : "semimajor",
		kira_get( st, KIRA_RINGSCALE ));

	} else if(!strcmp(swhat, "ringscale") || !strcmp(swhat, "scale")) {
	    if(sval)
		kira_set( st, KIRA_RINGSCALE, getfloat( sval, kira_get(st,KIRA_RINGSCALE) ) );
	    msg("kiractl ringscale %g", kira_get(st,KIRA_RINGSCALE));

	} else if(!strcmp(swhat, "span") || !strcmp(swhat, "ringspan")) {
	    if(argc>2 && (val = getfloat( argv[2], -1 )) >= 0)
		    kira_set( st, KIRA_RINGMIN, val );
	    if(argc>3 && (val = getfloat( argv[3], -1 )) >= 0)
		    kira_set( st, KIRA_RINGMAX, val );
	    msg("kiractl ringspan %.0f %.0f",
		kira_get( st, KIRA_RINGMIN ), kira_get( st, KIRA_RINGMAX ));

	} else if(!strncmp(swhat, "nod", 3) || !strncmp(swhat, "ring", 4)) {
	    what = swhat[0]=='n' ? KIRA_NODES : KIRA_RINGS;
	    if(sval)
		kira_set( st, what,
		     (0==strncmp(sval, "root", 4)) ? KIRA_ROOTS : getbool(sval, KIRA_ON) );
	    val = kira_get( st, what );
	    msg("kiractl %s %s",
		what==KIRA_NODES ? "nodes" : "rings",
		val==2 ? "root" : val==1 ? "on" : "off");

slevy's avatar
 
slevy committed
	} else if(!strncmp(swhat, "track", 2)) {
	    if(sval)
		kira_set( st, KIRA_TRACK, getbool(sval, 0) );
	    val = kira_get( st, KIRA_TRACK );
	    msg(val == 0 ? "kiractl track off" : "kiractl track %d", (int)val);

slevy's avatar
 
slevy committed
	} else {
slevy's avatar
 
slevy committed
	    msg("kiractl {node|ring} {on|off|root} | size {sep|semimaj} | scale <fac> | span <minpix> <maxpix> | track <id>");
slevy's avatar
 
slevy committed
	}
#endif

teuben's avatar
teuben committed
4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320
  } else if(!strncmp( argv[0], "lum", 3 )) {
	struct valdesc *vd;
	if(argc>1) {
	    if(!specks_set_byvariable( st, argv[1], &st->sizedby ))
		st->sizedby = CONSTVAL;
	}
	vd = &st->vdesc[st->curdata][st->sizedby];
	if(argc>2) vd->lall = 0, vd->lmin = getfloat(argv[2], vd->lmin);
	if(argc>3) vd->lall = 0, vd->lmax = getfloat(argv[3], vd->lmax);
	if(vd->lmin == vd->lmax && vd->lmin == 0 ||
	       argc>2 && (!strcmp(argv[2],"-") || !strcmp(argv[2],"all"))) {
	    vd->lmin = vd->min, vd->lmax = vd->max, vd->lall = 1;
	}
	if(st->sizedby == CONSTVAL) {
	    msg("lum-by constant %g", vd->lmin);
	} else {
	    msg("lum-by %d (%s) [%g..%g mean %.3g]", st->sizedby,
		vd->name, vd->lmin, vd->lmax, vd->mean);
	}
	if(argc>1)
	    st->sizeseq++;

  } else if(!strcmp( argv[0], "cmap" ) || !strcmp( argv[0], "boxcmap" )) {
	char *tfname = argv[1];
	char *realfile = findfile( NULL, argv[1] );
	if(argc != 2) {
	    msg("Usage: %s <ascii-file-of-RGB-triples>", argv[0]);
	} else {
	    if(argc > 1 && realfile == NULL) {
		tfname = (char *)alloca( strlen(argv[1]) + 6 );
		sprintf(tfname, "%s.cmap", argv[1]);
		realfile = findfile( NULL, tfname );
	    }
	    if(realfile != NULL) {
		if(argv[0][0] == 'b') {
		    specks_read_cmap( st, realfile, &st->boxncmap, &st->boxcmap );
		} else {
		    specks_read_cmap( st, realfile, &st->ncmap, &st->cmap );
		    st->colorseq++; /* Must recompute particle coloring */
		}
	    } else {
		msg("%s: can't find \"%s\" nor ....cmap", argv[0], argv[1]);
	    }
	}

  } else if(!strncmp( argv[0], "cment", 5 ) || !strncmp( argv[0], "boxcment", 8 )) {
	int i, r,g,b;
	unsigned char *cvp;
	int isbox = argv[0][0] == 'b';
	int ncmap = isbox ? st->boxncmap : st->ncmap;
	int *cmap = isbox ? st->boxcmap : st->cmap;

	if(argc>1) {
	    i = atoi(argv[1]);
	    if(i < 0 || i > ncmap) {
		msg("cment: %d out of range (must be 0..%d)", i, st->ncmap-1);
	    } else {
		if(argc>4) {
		    r=255*atof(argv[2]);
		    g=255*atof(argv[3]);
		    b=255*atof(argv[4]);
		    cmap[i] = PACKRGBA(r,g,b,0);
		    if(st->sl) st->colorseq++;	/* force recolor */
		}
		cvp = (unsigned char *)&cmap[i];
		msg("%scment %d  %.3f %.3f %.3f  (of cmap 0..%d",
			isbox ? "box" : "",
			i, cvp[0]/255., cvp[1]/255., cvp[2]/255.,
			st->ncmap-1);
	    }
	} else {
	    msg("Usage: %scment colorindex [r g b]", isbox ? "box" : "");
	}

  } else if(!strncmp( argv[0], "only", 4 )) {
	int plus = !strcmp(argv[0], "only+");
	int minus = !strcmp(argv[0], "only-");
	int exact = !strcmp(argv[0], "only=");
	int total = 0, chosen = 0, waschosen = 0;
	int tvar = -1;
	int all = 0;
	int k;
	int nspecks, nvisible, needthresh;
	struct specklist *sl;
	struct speck *sp;

	if(argc>1) {
	    if(!strcmp(argv[1], "all") || !strcmp(argv[1], "*")) all = 1;
	    if(!strcmp(argv[1], "none")) all = -1;
	}
	if(!all && ((!plus && !minus && !exact) ||
		!specks_set_byvariable( st, argv[1], &tvar))) {
	    msg("only+/only-/only=  \"all\"|\"none\"|varname value minvalue-maxvalue ...");
	}

	plus |= exact;

	if(exact || all) {
	    /* preset all values */
	    int wipebit = (plus && all>0) ? 0 : THRESHBIT;
	    for(sl = st->sl; sl != NULL; sl = sl->next) {
		if((sp = sl->specks) == NULL || sl->text != NULL)
		    continue;
		for(k = sl->nspecks; --k >= 0; sp = NextSpeck(sp, sl, 1))
		    sp->rgba = (sp->rgba & ~THRESHBIT) | wipebit;
	    }
	}

	/*
	 * If a thresh variable is given, use "plus" to choose
	 * whether to make that speck visible.
	 * NOTE that specks with THRESHBIT on are *invisible*!
	 */
	for(i = 2; i < argc; i++) {
	    float vmin = -1e38, vmax = 1e38;

	    if(argv[i][0] == '<') vmax = atof(argv[i]+1);
	    else if(argv[i][0] == '>') vmin = atof(argv[i]+1);
	    else {
		switch(sscanf(argv[i], "%f%*c%f", &vmin, &vmax)) {
		case 1: vmax = vmin; break;
		case 2: break;
		default:
		    msg("%s: \"%s\": expected N or <N or >N or N-M",
			argv[0], argv[i]);
		    return -1;
		}
	    }

	    for(sl = st->sl; sl != NULL; sl = sl->next) {
		if((sp = sl->specks) == NULL || sl->text != NULL || tvar<0
			|| SMALLSPECKSIZE(tvar+1) > sl->bytesperspeck)
		    continue;
		for(k = sl->nspecks; --k >= 0; sp = NextSpeck(sp, sl, 1)) {
		    if(sp->val[tvar]>=vmin && sp->val[tvar]<=vmax)
			sp->rgba = plus ? sp->rgba & ~THRESHBIT
					: sp->rgba | THRESHBIT;
		}
	    }
	}

	nspecks = nvisible = 0;
	for(sl = st->sl; sl != NULL; sl = sl->next) {
	    if((sp = sl->specks) == NULL || sl->text != NULL)
		continue;
	    nspecks += sl->nspecks;
	    for(k = sl->nspecks; --k >= 0; sp = NextSpeck(sp, sl, 1)) {
		if(!(sp->rgba & THRESHBIT))
		    nvisible++;
	    }
	}

	needthresh = (st->usethresh&P_USETHRESH)==0 && nvisible < nspecks;
	if(needthresh)
	    st->usethresh |= P_USETHRESH;
	msg("only %d visible of %d%s", nvisible, nspecks,
		needthresh ? " (selecting \"thresh on\")" : "");

  } else if(!strncmp( argv[0], "thresh", 6 )) {
	int i, expect = -1;
	float v;
	char smin[16], smax[16], *arg;
	int changed = 0;
	for(i = 1; i < argc; i++) {
	    arg = argv[i];
	    if(!strcmp(arg,"off"))
		st->usethresh &= ~P_USETHRESH, expect = -1;
	    else if(!strcmp(arg,"on"))
		st->usethresh |= P_USETHRESH, expect = -1;
	    else if(expect<0 && specks_set_byvariable(st, arg, &st->threshvar)) {
		expect = 0;
		st->usethresh = P_USETHRESH;	/* neither MIN nor MAX set */
		changed = 1;
	    } else if(!strcmp(arg,"min") || !strcmp(arg, ">"))
		expect = 0;
	    else if(!strcmp(arg,"max") || !strcmp(arg, "<"))
		expect = 1;
	    else if(!strcmp(arg,"="))
		expect = 2;
	    else if(!strcmp(arg, "-")) {
		st->usethresh &= expect ? ~P_THRESHMAX : ~P_THRESHMIN;
		expect = 1;
	    } else if(sscanf(arg, "%f", &v) > 0) {
		if(expect != 1) {	/* -1, 0, or 2 */
		    st->thresh[0] = v;
		    st->usethresh |= P_THRESHMIN | P_USETHRESH;
		    changed = 1;
		}
		if(expect >= 1) {	/* 1 or 2 */
		    st->thresh[1] = v;
		    st->usethresh |= P_THRESHMAX | P_USETHRESH;
		    changed = 1;
		}
		expect = 1;
	    } else if(sscanf(arg, ">%f", &st->thresh[0]) > 0) {
		st->usethresh |= P_THRESHMIN | P_USETHRESH;
		changed = 1;
	    } else if(sscanf(arg, "<%f", &st->thresh[1]) > 0) {
		st->usethresh |= P_THRESHMAX | P_USETHRESH;
		changed = 1;
	    } else if(sscanf(arg, "=%f", &st->thresh[0]) > 0) {
		st->thresh[1] = st->thresh[0];
		st->usethresh |= P_THRESHMIN | P_THRESHMAX | P_USETHRESH; 
		changed = 1;
	    }
	}
	if(changed) {
	    struct specklist *sl;
	    st->threshseq++;
	    for(sl = st->sl; sl != NULL; sl = sl->next)
		specks_rethresh( st, sl, st->threshvar );
	}
	sprintf(smin, st->usethresh&P_THRESHMIN ? "%g" : "-", st->thresh[0]);
	sprintf(smax, st->usethresh&P_THRESHMAX ? "%g" : "-", st->thresh[1]);
	msg(st->usethresh == 0 ? "thresh off"
		: "thresh%s %d(%s) min %s max %s",
		st->usethresh&P_USETHRESH ? "" : " off ",
		st->threshvar, st->vdesc[st->curdata][st->threshvar].name,
		smin, smax);
	
  } else if(!strcmp( argv[0], "rawdump" )) {
	FILE *f = argc>1 ? fopen(argv[1], "w") : NULL;
	struct specklist *sl;
	int i, j, nmine, net;
	if(f == NULL) {
	    msg("%s: can't create", argv[1]);
	} else {
	    fprintf(f, "# from dataset %d \"%s\" timestep %d\n",
		st->curdata, st->fname[st->curdata][st->curtime], st->curtime);
	    for(i = 0; st->vdesc[st->curdata][i].name[0] != '\0'; i++) {
		fprintf(f, "%s\t", st->vdesc[st->curdata][i].name);
	    }
	    fprintf(f, "\n");
	    nmine = i;
	    net = 0;
	    sl = specks_timespecks( st, st->curdata, st->curtime );
	    if(sl == NULL && st->ntimes <= 1)
		sl = st->sl;
	    for(; sl != NULL; sl = sl->next) {
		for(j = 0; j < sl->nspecks; j++) {
		    for(i = 0; i < nmine; i++)
			fprintf(f, "%g\t", (NextSpeck(sl->specks, sl, j)->val[i]));
		    fprintf(f, "\n");
		    net++;
		}
	    }
	    fclose(f);
	    msg("Wrote %d particles (%d fields each) to %s",
		net, nmine, argv[1]);
	}
	
  } else if(!strncmp( argv[0], "slum", 3 ) || !strcmp( argv[0], "scale-lum" )) {
	float v, *lp;
	int by = st->sizedby, cd = st->curdata;
	if(argc>1) {
	    if(argc>2) specks_set_byvariable( st, argv[2], &by );
	    if(argc>3) sscanf(argv[3], "%d", &cd);
	    if((unsigned int)cd >= MAXFILES) cd = 0;
	    lp = &st->vdesc[cd][by].lum;
	    *lp = getfloat( argv[1], *lp!=0 ? *lp : 1 );
	}
	msg("slum scaling %g (var %d %s dataset %s (%d))",
	    st->vdesc[cd][by].lum, by, st->vdesc[cd][by].name,
	    st->dataname[cd], cd);

  } else if(!strcmp( argv[0], "see" )) {
	if(argc>1) {
	    int v = -1, len = strlen(argv[1]);
		/* search st->dataname[] array! XXX */
	    if(sscanf(argv[1], "%d", &v) > 0) {
		/* fine */
	    } else {
		for(v = st->ndata; --v >= 0; ) {
		    if(!strncasecmp( argv[1], st->dataname[v], len ))
			break;
		}
		if(v < 0) {
		    char msgstr[2048];
		    sprintf(msgstr,
			"Never heard of dataset \"%s\"; we have these %d:",
			argv[1], st->ndata);
		    for(v = 0; v < st->ndata; v++)
			sprintf(msgstr+strlen(msgstr), " \"%s\"(%d) ", st->dataname[v], v);
		    msg(msgstr);
		    v = -1;
		}
	    }
	    if(v >= 0 && v < st->ndata)
		st->curdata = v;
	}
	msg("showing dataset %d (%s)", st->curdata,
		st->dataname[st->curdata]);

  } else if(!strcmp( argv[0], "show" ) || !strcmp( argv[0], "hide" )
	  || !strncasecmp( argv[0], "showbox", 7 )
	  || !strncasecmp( argv[0], "hidebox", 7 )) {
	int doshow = argv[0][0] == 's';
	int i, level, mask = 0;
	if(doshow) st->useboxes = 1;
	for(i = 1; i < argc; i++) {
	    level = getbool( argv[i], -2 );
	    if(level == -1)
		mask = ~0;
	    else if(level >= 0)
		mask |= (1 << level);
	}
	if(doshow) st->boxlevelmask |= mask;
	else st->boxlevelmask &= ~mask;
	/* st->boxlevelmask &= (1 << st->boxlevels) - 1;
	 *  Don't do this -- we want "show all" to apply to levels that
	 *  we haven't read in yet.
	 */
#if CAVEMENU
	for(i = 0; i < st->boxlevels && i < COUNT(ment.boxlevel); i++) {
	    struct boxleveler blr;
	    blr.st = st;
	    blr.level = i;
	    if(ment.boxlevel[i])
		set_boxlevel( 0, ment.boxlevel[i], &blr );
	}
#endif

  } else if(!strcmp( argv[0], "box" ) || !strcmp( argv[0], "boxes" )) {
	st->useboxes = getbool( argv[1], (st->useboxes+1)%3 );
	msg("boxes %s", st->useboxes==2 ? "ONLY"
				: st->useboxes ? "ON" : "off");

  } else if(!strncmp( argv[0], "boxlabel", 6 )) {
	if(argc>1) st->boxlabels = getbool( argv[1], !st->boxlabels );
	else st->boxlabels = !st->boxlabels;
	msg("boxlabels %s", st->boxlabels?"on":"off");

  } else if(!strncmp( argv[0], "boxaxes", 5 )) {
	if(argc>1) st->boxaxes = getbool( argv[1], !st->boxaxes );
	else st->boxaxes = !st->boxaxes;
	msg("boxaxes %s", st->boxaxes?"on":"off");

  } else if(!strcmp(argv[0], "boxscale")) {
	char buf[512];
	int from, to;
	if(argc>1) {
	    float v = atof(argv[1]);
	    from = getbool(argc>2?argv[2]:NULL, 0);
	    to = getbool(argc>3?argv[3]:NULL, MAXBOXLEV-1);
	    if(from<0) from = 0;
	    if(to>MAXBOXLEV-1) to=MAXBOXLEV-1;
	    while(from<=to)
		st->boxscale[from++] = v;
	}
	sprintf(buf, "boxscales:");
	for(to=MAXBOXLEV-1; to>1 && st->boxscale[to]==st->boxscale[to-1]; to--)
	    ;
	for(from = 0; from <= to; from++)
	    sprintf(buf+strlen(buf), " %g", st->boxscale[from]);
	msg("%s", buf);

  } else if(!strcmp( argv[0], "go" ) || !strcmp( argv[0], "gobox" )) {
	int boxno = getbool( argv[1], -1 );
	if(boxno < 0) {
	    msg("Usage: gobox <boxnumber> -- sets POI and jumps to view that AMR box");
	} else {
	    specks_gobox( st, boxno, argc-2, argv+2 );
	}

  } else if(!strcmp( argv[0], "goboxscale" )) {
	if(argc>1) sscanf(argv[1], "%f", &st->goboxscale);
	msg("goboxscale %g  (\"gobox\" sets scale to %g * box diagonal; 0 => leave scale intact)",
		st->goboxscale, st->goboxscale);

  } else if(!strcmp( argv[0], "psize" ) || !strcmp(argv[0], "pointsize")) {
	if(argc>1) {
	    st->psize = getfloat( argv[1], st->psize );
	    IFMENU( set_psize( st->psize, ment.psize, st ) );
	}
	msg("pointsize %g pixels (times scale-lum value)", st->psize );

  } else if(!strcmp( argv[0], "polysize" )) {
	if(argc>1) {
	    st->polysize = getfloat(argv[1], st->polysize);
	    IFMENU( set_polysize( st->polysize, ment.polysize, st ) );
	}
	if(argc>2) {
	    if(argv[2][0] == 'a' || argv[2][0] == 's') st->polyarea = 1;
	    else if(argv[2][0] == 'r') st->polyarea = 0;
	}
	msg("polysize %g%s", st->polysize, st->polyarea ? " area":"" );

  } else if(!strncmp( argv[0], "polylum", 7 )) {
	char *area = "";
	if(argc>1) {
	    if(!specks_set_byvariable( st, argv[1], &st->polysizevar ))
		if(!strcmp(argv[1], "-1") ||
		   !strcmp(argv[1], "point-size") ||
		   !strcmp(argv[1], "pointsize"))
		    st->polysizevar = -1;
	    if(st->polysizevar == CONSTVAL)
		st->polysizevar = -1;	/* Can't do const */
	}
	if(argc>2)
	    sscanf(argv[2], "%f", &st->polysize);
	switch(argv[argc-1][0]) {
	    case 'r':
	    case 'd':
	    case 's': st->polyarea = 0; break;
	    case 'a': st->polyarea = 1; break;
	}
	if(st->polyarea) area = " area";
	if(st->polysizevar < 0)
	    msg("polylumvar point-size%s", area);
	else
	    msg("polylumvar %d(%s)%s", st->polysizevar,
		st->vdesc[st->curdata][st->polysizevar].name,
		area);

  } else if(!strncmp( argv[0], "polyminpixels", 7 )) {
	if(argc>1) st->polymin = getfloat(argv[1], st->polymin);
	if(argc>2) st->polymax = getfloat(argv[2], st->polymax);
	msg("polyminpixels %g %g (minpixels maxpixels)", st->polymin, st->polymax );

  } else if(!strncmp( argv[0], "labelminpixels", 7 )) {
	st->textmin = getfloat(argv[1], st->textmin);
	msg("labelminpixels %g", st->textmin );


  } else if(!strcmp( argv[0], "labelsize" ) || !strcmp(argv[0], "lsize")) {
	st->textsize = getfloat(argv[1], st->textsize);
	msg("labelsize %g", st->textsize);

  } else if(!strcmp( argv[0], "point" ) || !strcmp( argv[0], "points" )) {
	st->usepoint = getbool( argv[1], !st->usepoint );
	IFMENU( set_point( st->usepoint, ment.point, st ) );
	msg("points %s", st->usepoint ? "on":"off");

  } else if(!strcmp( argv[0], "poly" ) || !strncmp( argv[0], "polygon", 7 )) {
	st->usepoly = getbool( argv[1], !st->usepoly );
	IFMENU( set_poly( st->usepoly, ment.poly, st ) );
	msg("polygons %s", st->usepoly ? "on":"off");

  } else if(!strncmp( argv[0], "texture", 7 ) || !strcmp( argv[0], "tx" )) {
	if(argc > 1 && !strcmp(argv[1], "preload")) {
	    msg("Preloading textures...");
	    for(i = 0; i < st->ntextures; i++)
		if(st->textures[i]) txload( st->textures[i] );
	} else {
	    st->usetextures = getbool( argv[1], !st->usetextures );
	    msg("textures %s", st->usetextures ? "on":"off");
	}

  } else if(!strcmp( argv[0], "txscale" )) {
	if(argc>1) sscanf(argv[1], "%f", &st->txscale);
	msg("txscale %.3f", st->txscale);

  } else if(!strncmp( argv[0], "polyorivar", 10)) {
	if(argc>1) st->polyorivar0 = getbool(argv[1], -1);
	msg(st->polyorivar0 >= 0
	  ? "polyorivar %d : polygon orientations from var %d..%d"
	  : "polyorivar %d : polygon orientations parallel to screen",
	  st->polyorivar0, st->polyorivar0, st->polyorivar0+5);

  } else if(!strcmp( argv[0], "texturevar")) {
	if(argc>1) st->texturevar = getbool(argv[1], -1);
	msg("texturevar %d", st->texturevar);

  } else if(!strcmp( argv[0], "label" ) || !strcmp( argv[0], "labels" )) {
	st->usetext = getbool( argv[1], !st->usetext );
	IFMENU( set_label( st->usetext, ment.label, st ) );
	msg("labels %s", st->usetext ? "on":"off");

  } else if(!strcmp( argv[0], "laxes" )) {
	st->usetextaxes = getbool(argv[1], !st->usetextaxes);
	msg("laxes %s  (axes on each label)", st->usetextaxes ? "on":"off");

  } else if(!strncmp( argv[0], "polyside", 8 )) {
	if(argc>1) sscanf(argv[1], "%d", &st->npolygon);
	msg("polysides %d (polygons drawn with %d sides)",
		st->npolygon,st->npolygon);

  } else if(!strcmp( argv[0], "gamma" )) {
	if(argc>1) sscanf(argv[1], "%f", &st->gamma);
	msg("gamma %g", st->gamma);

  } else if(!strcmp( argv[0], "alpha" )) {
	if(argc>1) sscanf(argv[1], "%f", &st->alpha);
	IFMENU( set_alpha( st->alpha, ment.alpha, st ) );
	msg("alpha %g", st->alpha);

  } else if(!strncmp( argv[0], "fast", 4 )) {
	st->fast = getbool(argv[1], !st->fast);
	if(argc > 2) sscanf(argv[2], "%f", &st->pfaint);
	if(argc > 3) sscanf(argv[3], "%f", &st->plarge);
	msg("fast %s  (faintest %.3g largest %.3g)",
		st->fast ? "on" : "off (better rendering)",
		st->pfaint, st->plarge);

  } else if(!strcmp( argv[0], "fog" ) && argc > 1) {
	IFMENU( set_fog( st->fog, ment.fog, st ) );

  } else if(!strcmp( argv[0], "menu" ) || !strcmp( argv[0], "fmenu" )) {
#if CAVEMENU
	if(argc == 4 && !strncmp(argv[1], "pos", 3)) {
	    menu_setpos( pmenu, atof(argv[2]), atof(argv[3]) );

	} else if(argc == 3 && !strcmp(argv[1], "wall")) {
	    menu_onwall( pmenu, atoi(argv[2]) );
	    menu_onwall( stubmenu, atoi(argv[2]) );

	} else if(argc >= 2 && !strncmp(argv[1], "hid", 3)) {
	    st->hidemenu = argc>2 ? getbool( argv[2], st->hidemenu ) :
			 st->hidemenu ? 0 : -1;
	    msg("fmenu hide %s", st->hidemenu ? "on" : "off");

	} else if(argc >= 2 && !strcmp(argv[1], "show")) {
	    st->hidemenu = argc>2 ? getbool( argv[2], st->hidemenu ) :
			st->hidemenu ? 0 : -1;
	    msg("fmenu show %s", st->hidemenu ? "off" : "on");

	} else if(argc >= 2 && !strncmp(argv[1], "h", 1)) {
	    if(argc>2) menu_setheight( pmenu, atof(argv[2]) );
	    msg("fmenu height %g", pmenu->yspan);

	} else if(argc >= 2 && !strncmp(argv[1], "demandfps", 6)) {
	    if(argc == 3) sscanf(argv[2], "%f", &st->menudemandfps);
	    msg("fmenu demandfps %g", st->menudemandfps);

	} else if(argc > 1 && argc <= 3 && !strncmp(argv[1], "font", 4)) {
	    if(argc > 2) {
		menu_setfont( pmenu, argv[2] );
		menu_setfont( stubmenu, argv[2] );
	    }
	    msg("fmenu font %s", menu_getfont(pmenu));

	} else if(argc == 2 && atof(argv[1]) != 0) {
	    menu_setheight( pmenu, atof(argv[1]) );

	} else if(argc == 3) {
	    menu_setpos( pmenu, atof(argv[1]), atof(argv[2]) );

	} else if(argc <= 1 || argv[1][0] == '?' || !strncmp(argv[1],"help",4)) {
	    msg("\"fmenu ?\" for help");
	    msg("fmenu pos %.3f %.3f (xpos ypos, in pixels)", pmenu->x0, pmenu->y0);
	    msg("fmenu height %.3f (fraction of screen height; negative = inverted)", pmenu->yspan);
	    msg("fmenu wall %d (cave.h wall number)", pmenu->cavewall);
	    msg("fmenu hide [on|off]");
	    msg("fmenu font %s (X font name)", menu_getfont(pmenu));
	    msg("fmenu demandfps %g (hide scene if FPS too low)", st->menudemandfps);
	} else {
	    msg("fmenu: expected \"pos\" or \"height\" or \"hide\" or \"font\" or \"wall\".");
	    msg("Try just \"fmenu\" for help.");
	}
#endif

  } else if(!strcmp( argv[0], "datascale" ) && argc>1) {
	float s, v = atof(argv[1]);
	if(v!=0) {
	    struct specklist *sl;
	    struct speck *p;
	    int i;
	    for(sl = st->sl; sl != NULL; sl = sl->next) {
		s = v / sl->scaledby;
		for(i = 0, p = sl->specks; i < sl->nspecks; i++, p = NextSpeck(p, sl, 1)) {
		    p->p.x[0] *= s; p->p.x[1] *= s; p->p.x[2] *= s;
		}
		sl->scaledby = v;
	    }
	}
  } else if(!strcmp( argv[0], "where" ) || !strcmp( argv[0], "w" )) {
	tellwhere(st);

  }
  else
    return 0;

  *stp = st;	/* in case it changed, e.g. because of an "object" cmd */
  return 1;
}