Skip to content
Snippets Groups Projects
warp.c 30.79 KiB
#ifdef USE_WARP
/*
 * Time-dependent warp of a particle field,
 * for fake differentially-rotating galaxies.
 * Stuart Levy, slevy@ncsa.uiuc.edu
 * National Center for Supercomputing Applications,
 * University of Illinois 2001.
 * This file is part of partiview, released under the
 * Illinois Open Source License; see the file LICENSE.partiview for details.
 */

#ifdef WIN32
# include "winjunk.h"
#endif

#if unix
# include <unistd.h>
#endif

#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
# undef isspace
# undef isdigit

#ifdef HAVE_VALUES_H
# include <values.h>  /* for MAXFLOAT */
#endif

/* In case values.h doesn't define MAXFLOAT, e.g. on mingw32, ... */
#ifndef MAXFLOAT
# define MAXFLOAT  3e38
#endif

#include "shmem.h"
#include "specks.h"
#include "partiviewc.h"

#include "textures.h"		/* for get_dsp_context() */

static char local_id[] = "$Id$";

struct speckcache {
    float tfrac;
    struct specklist *sl;
};

#define COORD_DISK   0
#define COORD_WORLD  (-1)
#define COORD_OBJECT (-2)

typedef enum { DIFFROT, EXTRAPOLATE, PROJECT } warpstyle_t;


struct svvec {
    struct cf {
	int c;
	float f;
    } *v;
    int len, room, maxc;
};

struct warpstuff {
    struct stuff *st;

    warpstyle_t style;

    /* for style == DIFFROT (differential rotation in XZ plane) */
    float tfrom, tto;
    float zerotime;
    int has_trange, has_zerotime;
    float period0;
    float tunit;
    float rcore, routercore;
    int core_coordsys;
    float rcoredisk, routercoredisk;
    double fixedp[3];
    float rfixed;
    int has_fixed, fixed_coordsys;
    float fixedrdisk;
    float rigidrot;
    double d2o[16], o2d[16];
    int has_d2o, has_o2d;

    /* style == EXTRAPOLATE ("-extrap coef0[,degree]") */
    /* Do polynomial extrapolation. */
    int degree;
    int coef0;	/* first fieldnumber of 3*degree terms: x',y',z', x'',y'',z'',... */

    /* style == PROJECT (from N dimensions to 3-D) */
    struct svvec sref;
    struct svvec sproj[3];
    struct svvec sadd;
    float sfadd[3];

    int valid;
    int locked;

    float tfrac, dtfrac;
    double realtime;
    float fixomega;

    struct speckcache slc[MAXDSPCTX];
};

static float fastsin2pi( float v );
static void inittables(void);
static struct specklist *warp_get_parti( struct dyndata *dd, struct stuff *st, double realtime );
static int warp_get_trange( struct dyndata *dd, struct stuff *st, double *tminp, double *tmaxp, int ready );
static int warp_parse_args( struct dyndata *dd, struct stuff *st, int argc, char **argv );
struct warpstuff *warp_setup( struct stuff *st, struct warpstuff *ws, int argc, char **argv );

static int warp_coordsys( const char *str ) {
    if(strchr(str, 'w')) return COORD_WORLD;
    if(strchr(str, 'o')) return COORD_OBJECT;
    if(strchr(str, 'd')) return COORD_DISK;
    return COORD_DISK;
}

int parsematrix( double tfm[16], char *str, const char *whose )
{
    double m[16];
    Matrix T;
    Point xyz;
    float aer[3];
    float scl = 1.0;
    int k = sscanf(str, 
      "%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf%*c%lf",
      &m[0],&m[1],&m[2],&m[3],
      &m[4],&m[5],&m[6],&m[7],
      &m[8],&m[9],&m[10],&m[11],
      &m[12],&m[13],&m[14],&m[15]);

    switch(k) {

    case 1:	/* simple scaling */
	memset(tfm, 0, 16*sizeof(double));
	tfm[0*4+0] = tfm[1*4+1] = tfm[2*4+2] = m[0];
	tfm[3*4+3] = 1;
	return 1;

    case 7:	/* virdir style Tx Ty Tz Rx Ry Rz Scl */
	scl = m[6];	/* and fall into ... */

    case 6:	/* virdir style Tx Ty Tz Rx Ry Rz */
	xyz.x[0] = m[0];
	xyz.x[1] = m[1];
	xyz.x[2] = m[2];
	aer[1] = m[3];
	aer[0] = m[4];
	aer[2] = m[5];
	xyzaer2tfm( &T, &xyz, aer );
	for(k = 0; k < 3*4; k++)
	    tfm[k] = T.m[k] * scl;
	for(k = 3*4; k < 4*4; k++)
	    tfm[k] = T.m[k];
	return 1;

    case 9:	/* 3x3 linear */
	memset(tfm, 0, 16*sizeof(double));
	for(k = 0; k < 3; k++) {
	    tfm[k*4+0] = m[k*3+0];
	    tfm[k*4+1] = m[k*3+1];
	    tfm[k*4+2] = m[k*3+2];
	}
	tfm[3*4+3] = 1;
	return 1;

    case 16:
	memcpy(tfm, m, 16*sizeof(double));
	break; /* great */

    default:
	if(whose)
	    msg("%s %s: expected 1 or 6 or 7 or 9 or 16 numbers", whose, str);
	return 0;
    }
    return 1;
}

static void svneed( struct svvec *sv, int need )
{
    if(need < sv->room)
	return;
    sv->room = 3 + 2 * (need > sv->room ? need : sv->room);
    sv->v = (struct cf *) (sv->v ? malloc( sv->room * sizeof(*sv->v) )
	    		     : realloc( sv->v, sv->room * sizeof(*sv->v) ));
    memset( &sv->v[sv->len], 0, (sv->room - sv->len) * sizeof(*sv->v) );
}

static void svinit( struct svvec *sv )
{
    memset(sv, 0, sizeof(*sv));
}

static void svfree( struct svvec *sv )
{
    if(sv && sv->v)
	free(sv->v);
}

struct cf *svvref( struct svvec *sv, int index )
{
    if(index < 0)
	return NULL;
    if(index >= sv->room)
	svneed( sv, index+1 );
    if(sv->len <= index)
	sv->len = index+1;
    return &sv->v[index];
}

static int projvec( struct warpstuff *ws, struct svvec *sv, int arg, int argc, char **argv )
{
    int ix, ok, a;
    char *cp, *ep;
    int err = 0;
    int i = 0;
    int coef = 0;
    int xindex = &(((struct speck *)0)->p.x[0]) - &(((struct speck *)0)->val[0]);

    svinit( sv );
    ws->style = PROJECT;
    sv->maxc = -1;
    a = arg;
    /*
     * expect:  [integer-or-fieldname ":"] float float ... {EOF | -<non-number>}
     */
    cp = argv[a];
    do {
	double v;
	char *colon;
	struct cf *cfp;

	while(*cp == ',' || *cp == ':' || isspace(*cp))
	    cp++;

	if(*cp == '\0') {
	    if(++a >= argc)
		break;
	    if(argv[a][0] == '-' && !(argv[a][1] == '.' || isdigit(argv[a][1])))
		break;
	    cp = argv[a];
	    continue;
	}

	colon = strpbrk(cp, ":, \t");
	if(colon && *colon == ':') {
	    *colon = '\0';
	    if(colon == cp+1 && *cp>='x' && *cp<='z') {
		coef = xindex + (*cp - 'x');
	    } else {
		err = !specks_set_byvariable( ws->st, cp, &coef );
	    }
	    *colon = ':';
	    if(err) {
		msg("warp %s: expected {<integer>|<fieldname>|x|y|z}: coef, coef, ..., not %s",
			argv[arg-1], cp);
		break;
	    }
	    cp = colon+1;
	    continue;
	}

        v = strtod(cp, &ep);

	if(ep == cp) {
	    err = 1;
	    break;
	}
	cp = ep;

	cfp = svvref( sv, i );
	cfp->f = v;
	cfp->c = coef;
	if(sv->maxc < coef)
	    sv->maxc = coef;
	i++;
	coef++;
	if(coef == xindex+3) coef = 0;

    } while(!err);

    return err ? argc+1 : a;
}

static void projadd( struct warpstuff *ws )
{
    int i, k, r;

    for(k = 0; k < 3; k++) {
	float v = ws->sadd.len>k ? ws->sadd.v[k].f : 0;
	for(r = 0; r < ws->sref.len; r++) {
	    int cref = ws->sref.v[r].c;
	    float fref = ws->sref.v[r].f;
	    struct svvec *sproj = &ws->sproj[k];
	    for(i = 0; i < sproj->len; i++) {
		if(sproj->v[i].c == cref)
		    v -= sproj->v[i].f * fref;
	    }
	}
	ws->sfadd[k] = v;
    }
}

static void warp_invalidate( struct stuff *st, struct warpstuff *ws )
{
    int i;
    st->dyn.slvalid = 0;
    for(i=0; i<MAXDSPCTX; i++)
	ws->slc[i].tfrac = -MAXFLOAT;
}
    

struct warpstuff *warp_setup( struct stuff *st, struct warpstuff *oldws, int argc, char **argv )
{
  struct warpstuff *ws;
  struct warpstuff tws;
  int i, c;
  char key;
  float v;

  inittables();

  if(oldws)
    tws = *oldws;
  else
    memset(&tws, 0, sizeof(tws));
  tws.st = st;

  for(i = 1; i < argc-1; i += 2) {
    char *optarg = argv[i+1];
    if(!strncmp(argv[i], "-w", 2) && argv[i][2] >= 'x' && argv[i][2] <= 'z') {
	i = projvec( &tws, &tws.sproj[ argv[i][2] - 'x' ], i+1, argc, argv ) - 2;

    } else if(!strcmp(argv[i], "-ref")) {
	i = projvec( &tws, &tws.sref, i+1, argc, argv ) - 2;

    } else if(!strcmp(argv[i], "-add")) {
	i = projvec( &tws, &tws.sadd, i+1, argc, argv ) - 2;
	if(tws.sadd.len != 3 || tws.sadd.v[0].c != 0 || tws.sadd.v[1].c != 1 || tws.sadd.v[2].c != 2) {
	    msg("warp -add: expected dx,dy,dz");
	    tws.sadd.len = 0;
	}

    } else if(!strncmp(argv[i], "-extrapolate", 3)) {
	char coef0name[32];
	tws.style = EXTRAPOLATE;
	tws.degree = 1;
	coef0name[0] = '\0';
	sscanf(optarg, "%31[^,],%d", coef0name, &tws.degree);
	if(!specks_set_byvariable( st, coef0name, &tws.coef0 )) {
	    tws.degree = 0;
	    if(0!=strcmp(coef0name, "off")) {
		msg("warp -extrap: expected fieldnumber or name, not \"%s\"", coef0name);
	    }
	}

    } else if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "-trate")) {
	sscanf(optarg, "%f", &tws.period0);
    } else if(!strncmp(argv[i], "-seconds", 4)) {
	ws->tunit = 1.0;
    } else if(!strncmp(argv[i], "-frames", 3)) {
	ws->tunit = 30.0;
    } else if(!strcmp(argv[i], "-f")) {
	tws.has_trange = sscanf(optarg,"%f%*c%f",&tws.tfrom,&tws.tto);
    } else if(!strcmp(argv[i], "-z")) {
	tws.has_zerotime = sscanf(optarg, "%f", &tws.zerotime);
    } else if(!strcmp(argv[i], "-t")) {
	sscanf(optarg, "%f%*c%f", &tws.tfrac, &tws.dtfrac);
    } else if(!strncmp(argv[i], "-fix", 4)) {
	tws.has_fixed = 0;
	tws.fixed_coordsys = warp_coordsys( optarg );
	switch(sscanf(optarg, "%lf%*c%lf%*c%lf",
			&tws.fixedp[0], &tws.fixedp[1], &tws.fixedp[2])) {
	case 1: tws.has_fixed = 1; break;
	case 3: tws.has_fixed = 3; break;
	default:
	    msg("warp -fix {x,y,z|radius}[w] not %s", optarg);
	}
	
    } else if(!strcmp(argv[i], "-r")) {
	tws.core_coordsys = warp_coordsys( optarg );
	switch(sscanf(optarg, "%f%c%f", &tws.rcore, &key, &v)) {
	case 3:
	   if(key == '-') tws.routercore = v;
	   else tws.routercore = tws.rcore*(1+v);
	   break;
	case 1:
	   tws.routercore = tws.rcore * 1.1;
	   break;
	default:
	   argc = 0;
	}
    } else if(!strcmp(argv[i], "-T")) {
	tws.has_o2d = parsematrix( tws.o2d, optarg, "warp -T" );

    } else if(!strcmp(argv[i], "-F")) {
	tws.has_d2o = parsematrix( tws.d2o, optarg, "warp -F" );

    } else if(!strcmp(argv[i], "-rigidrot") || !strcmp(argv[i], "-R")) {
	sscanf(optarg, "%g", &tws.rigidrot);	/* given as degrees */
	tws.rigidrot /= 360;	/* internally, kept as fraction of 1 turn */
    } else {
	argc = -1;
	break;
    }
  }
  if(argc < 3) {
    msg("Usage: %s  [-extrap coef0[,degree]] [-f fin,fout][-p period0[f|s]][-z zerotime][-R rot][-T o2d][-F d2o] [-r rcore[,transition][w]]] [-fix x,y,z[w]|radius[w]]", argv[0]);
    msg("-r : core rad. w/optional width of transition region (fraction of rcore)");
    msg("   : or (\"-\" separator) -r innercore-outercore");
    msg("-R(or -rigidrot) : add rigid rotation by \"rot\" degrees to entire body.");
    msg("-extrap coef0[,degree] : instead of above, extrapolate position by:");
    msg(" p=p0+(t/period0)*field[coef0..coef0+2]+(t/period0)^2*[coef0+3..coef0+5]+...");
    msg("-T, -F  sixteen numbers.  If only -T or -F is given, other is computed.");
    msg("  \"o2d\" = object-to-disk tfm, where disk center=0,0,0, in XZ plane.");
    msg("-fix x,y,z[w]  or  -fix r[w] (fix point or radius); \"w\" = \"world coords\".");
    msg(" sense: p' = p * o2d * warp(X,Z plane) * d2o");
    return NULL;
  }

  if(tws.style == PROJECT)
      projadd( &tws );		/* fill in sfadd[] terms: sum of sadd[] and sref dot sproj */

  ws = oldws ? oldws : NewN( struct warpstuff, 1 );
  if(tws.tunit == 0)
      tws.tunit = (tws.style == DIFFROT) ? 30.0 : 1.0;	/* ugly backward compat. */
  *ws = tws;
  warp_invalidate( st, ws );		/* Force recomputing parameters */
  return ws;
}

float slerp( float frac, float from, float to )
{
  if(frac<=0) return from;
  if(frac>=1) return to;
  return from + frac*frac*(3-2*frac)*(to-from);
}

void windup( struct warpstuff *ws, Point *out, CONST Point *in )
{
  /*
   * v(r) =  (vmax/rcore)*r  r in 0..rcore
   *         vmax	      r > rcore
   * period(r) = 2pi/(vmax/rcore)  r<rcore  [Call this T0.]
   *		 2pi/(vmax/r) = (r/rcore)*T0
   * theta(t/T0,r) = (t/T0)*T0*(r < rcore ? 1 : rcore/r)
   * So rotate in the XZ plane about (0,0,0) by theta(t/T0,r)
   */
    float x = in->x[0];
    float z = in->x[2];
    float r = hypotf( x, z );
    float omega =  (r<=ws->rcoredisk) ? 1
		: (r>=ws->routercoredisk) ? ws->routercoredisk/r
		: slerp( 1+(r-ws->routercoredisk)/(ws->routercoredisk-ws->rcoredisk),
			1, ws->routercoredisk/r );
    float theta, s, c;

    theta = (omega - ws->fixomega) * ws->tfrac + ws->rigidrot;
    s = fastsin2pi( theta );
    c = fastsin2pi( theta + 0.25 );

    out->x[0] = x*c - z*s;
    out->x[1] = in->x[1];
    out->x[2] = x*s + z*c;
}

void dvtfmpoint( Point *dst, CONST Point *src, CONST double T[16] )
{
  int i;
  for(i = 0; i < 3; i++)
    dst->x[i] = src->x[0]*T[i] + src->x[1]*T[i+4] + src->x[2]*T[i+8] + T[i+12];
}

void windup_o2d( struct warpstuff *ws, Point *out, CONST Point *in ) {
    Point tin, tout;

    dvtfmpoint( &tin, in, ws->o2d );
    windup( ws, &tout, &tin );
    dvtfmpoint( out, &tout, ws->d2o );
}

void extrap( struct warpstuff *ws, Point *out, CONST Point *in, CONST struct speck *sp, int deg )
{
    int i;
    float t = ws->tfrac;
    CONST float *coefval = &sp->val[ws->coef0];
    if(deg == 1) {
	out->x[0] = in->x[0] + coefval[0]*t;
	out->x[1] = in->x[1] + coefval[1]*t;
	out->x[2] = in->x[2] + coefval[2]*t;
    } else {
	*out = *in;
	for(i = 0; i < deg; i++, coefval += 3, t *= ws->tfrac) {
	    out->x[0] += coefval[0]*t;
	    out->x[1] += coefval[1]*t;
	    out->x[2] += coefval[2]*t;
	}
    }
}

void extrap_o2d( struct warpstuff *ws, Point *out, CONST struct speck *sp, int deg )
{
    Point tin, tout;

    dvtfmpoint( &tin, &sp->p, ws->o2d );
    extrap( ws, &tout, &tin, sp, deg );
    dvtfmpoint( out, &tout, ws->d2o );
}

void warpspecks( struct warpstuff *ws,
		 struct stuff *st,
		 struct specklist *osl,
		 struct specklist *sl )
{
  struct speck *osp, *sp;
  int i, n;

  if(osl->specks == NULL) return;
  n = osl->nspecks;
  /* memcpy( sl->specks, osl->specks, osl->bytesperspeck * n ); */
  osp = osl->specks;
  sp = sl->specks;
  switch(ws->style) {
  case EXTRAPOLATE: {
	int deg = ws->degree;
	int excess = SMALLSPECKSIZE( ws->degree*3 + ws->coef0 ) - sl->bytesperspeck;
	if(excess > 0)
	    deg -= (excess+2) / 3;
	    
	for(i = 0; i < n; i++) {
	    if(ws->has_o2d)
		extrap_o2d( ws, &sp->p, osp, deg );
	    else
		extrap( ws, &sp->p, &osp->p, osp, deg );
	    osp = NextSpeck(osp, osl, 1);
	    sp = NextSpeck(sp, sl, 1);
	}
    }
    break;
  case DIFFROT:
    for(i = 0; i < n; i++) {
	if(ws->has_o2d)
	    windup_o2d( ws, &sp->p, &osp->p );
	else
	    windup( ws, &sp->p, &osp->p );
	osp = NextSpeck(osp, osl, 1);
	sp = NextSpeck(sp, sl, 1);
    }
    break;

  case PROJECT: {
	int k;
	int maxval = SPECKMAXVAL(osl);
	int maxcoef = ws->sproj[0].maxc > ws->sproj[1].maxc ? ws->sproj[0].maxc : ws->sproj[1].maxc;
	if(maxcoef < ws->sproj[2].maxc)
	    maxcoef = ws->sproj[2].maxc;

	if(maxcoef >= maxval) {
	    /* Can't transform, or identity tfm */
	    for(i = 0; i < n; i++) {
		sp->p = osp->p;
		osp = NextSpeck(osp, osl, 1);
		sp = NextSpeck(sp, sl, 1);
	    }
	} else {
	    for(i = 0; i < n; i++) {
		for(k = 0; k < 3; k++) {
		    struct svvec *sproj = &ws->sproj[k];
		    float *oval = &osp->val[0];
		    float v = ws->sfadd[k];
		    if(sproj->len == 0) {
			v += osp->p.x[k];
		    } else {
			int m;
			for(m = 0; m < sproj->len; m++)
			    v += osp->val[ sproj->v[m].c ] * sproj->v[m].f;
		    }
		    sp->p.x[k] = v;
		}
		osp = NextSpeck(osp, osl, 1);
		sp = NextSpeck(sp, sl, 1);
	    }
	}
    }
    break;

  default:
    msg("warp: warped style %d", ws->style);
  }
}

static float warpfrac( struct warpstuff *ws, double time )
{
  if(ws->has_trange == 2 && ws->tfrom != ws->tto) {
    return slerp( (time - ws->tfrom) / (ws->tto - ws->tfrom), -1, 1 );
  } else if(ws->period0 != 0) {
    return (time - ws->tfrom) / ws->period0;
  } else {
    return 0;
  }
}

static double dvlength( double v[3] ) {
  return sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );
}

static void ddmmul( double dest[16], Matrix *a, double b[16] ) {
    int i,j;
    for(i = 0; i < 4; i++) {
	for(j = 0; j < 4; j++) {
	    dest[i*4+j]	= a->m[i*4+0]*b[0*4+j]
			+ a->m[i*4+1]*b[1*4+j]
			+ a->m[i*4+2]*b[2*4+j]
			+ a->m[i*4+3]*b[3*4+j];
	}
    }
}

static void dvmmul( double dest[3], double src[3], double T[16] ) {
    double w = src[0]*T[0*4+3] + src[1]*T[1*4+3] + src[2]*T[2*4+3] + T[3*4+3];
    w = 1/w;
    dest[0] = (src[0]*T[0*4+0] + src[1]*T[1*4+0] + src[2]*T[2*4+0] + T[3*4+0])*w;
    dest[1] = (src[0]*T[0*4+1] + src[1]*T[1*4+1] + src[2]*T[2*4+1] + T[3*4+1])*w;
    dest[2] = (src[0]*T[0*4+2] + src[1]*T[1*4+2] + src[2]*T[2*4+2] + T[3*4+2])*w;
}
static int getT2d( double T2d[16], struct stuff *st, struct warpstuff *ws, int coordsys )
{
    Matrix o2w, w2o;
    switch(coordsys) {
    case COORD_DISK:
	return 0;
    case COORD_OBJECT:
	if(!ws->has_o2d)
	    return 0;
	memcpy( T2d, ws->o2d, 16*sizeof(double) );
	return 1;
    case COORD_WORLD:
	parti_geto2w( st, parti_idof(st), &o2w );
	eucinv( &w2o, &o2w );
	if(ws->has_o2d) {
	    ddmmul( T2d, &w2o, ws->o2d );
	} else {
	    int i;
	    for(i = 0; i < 4*4; i++)
		T2d[i] = w2o.m[i];
	}
	return 1;
    }
    msg("warp getT2d %d", coordsys);
    return 0;
}

static double rtodisk( struct stuff *st, struct warpstuff *ws, double radius, int coordsys ) {
  double w2d[16];
  switch(coordsys) {
  case COORD_DISK:
	return radius;
  case COORD_OBJECT:
	return (ws->has_o2d ?  radius * dvlength( &ws->o2d[0] ) : radius);
  case COORD_WORLD:
	getT2d( w2d, st, ws, COORD_WORLD );
	return radius * dvlength( &w2d[0] );
  }
  msg("warp: rtodisk");
  return 0;
}

/* Delay computing transformable quantities until we really need them */
static void setup_coords( struct stuff *st, struct warpstuff *ws ) {
    double fixed2d[16];
    double fixedpdisk[3];

    if(!ws->valid) {
	ws->rcoredisk = rtodisk( st, ws, ws->rcore, ws->core_coordsys );
	ws->routercoredisk = rtodisk( st, ws, ws->routercore, ws->core_coordsys );
	switch(ws->has_fixed) {
	case 1:
	    ws->fixedrdisk = rtodisk( st, ws, ws->fixedp[0], ws->fixed_coordsys );
	    break;
	case 3:
	    if(getT2d( fixed2d, st, ws, ws->fixed_coordsys )) {
		dvmmul( fixedpdisk, ws->fixedp, fixed2d );
		ws->fixedrdisk = hypot( fixedpdisk[0], fixedpdisk[2] );
	    } else {
		ws->fixedrdisk = hypot( ws->fixedp[0], ws->fixedp[2] );
	    }
	    break;
	}
	ws->valid = 1;
    }
    if(ws->has_fixed) {
	ws->fixomega =
	    (ws->fixedrdisk <= ws->rcoredisk) ? 1
	    : (ws->fixedrdisk >= ws->routercoredisk) ?
			ws->routercoredisk / ws->fixedrdisk
	    : slerp( 1 + (ws->fixedrdisk - ws->routercoredisk)
			/ (ws->routercoredisk - ws->rcoredisk),
		 1, ws->routercoredisk / ws->fixedrdisk );
    } else {
	ws->fixomega = 0;
    }
}


#define MAXINTERP 1024

float sintbl[MAXINTERP+1];

static void inittables(void) {
  int i;

  for(i = 0; i <= MAXINTERP; i++)
    sintbl[i] = sinf(2*M_PI*i / MAXINTERP);
}

/* fastsin2pi( x ) returns approximately sin( 2*pi*x ),
 * i.e. sin(x) if x is given as a fraction of a full 360-degree turn.
 */
static float fastsin2pi( float v ) {
  int i;
  while(v < 0) v += 1;
  while(v > 1) v -= 1;
  v *= MAXINTERP;
  i = (int)v;
  return sintbl[i] + (v - i)*(sintbl[i+1]-sintbl[i]);
}

void deucinv( double dst[16], CONST double src[16] )
{
    int i, j;
    double s = src[0]*src[0] + src[1]*src[1] + src[2]*src[2];
    Point trans;
    double t[16];
    if(src == dst) {
	memcpy( t, src, sizeof(t) );
	src = t;
    }
    for(i = 0; i < 3; i++) {
	for(j = 0; j < 3; j++)
	    dst[i*4+j] = src[j*4+i] / s;
	dst[i*4+3] = 0;
    }
    for(i = 0; i < 3; i++)
	dst[3*4+i] = -(src[3*4+0]*dst[0*4+i] + src[3*4+1]*dst[1*4+i] + src[3*4+2]*dst[2*4+i]);
    dst[3*4+3] = 1;
}


#ifndef STANDALONE

static char *projvec2str( struct stuff *st, char *str, char *end, char *prefix, struct svvec *sv )
{
    int xindex = &(((struct speck *)0)->p.x[0]) - &(((struct speck *)0)->val[0]);
    int len = prefix ? strlen(prefix) : 0;
    char *pre = prefix;
    int i;

    if(sv == NULL || sv->len == 0)
	return str;
    if(str == NULL || str+len+4 >= end)
	return NULL;
    for(i = 0; i < sv->len; i++) {
	int c = sv->v[i].c;
        struct valdesc *vd;
    	if(c>=xindex && c<=xindex+2) {
	    str += snprintf(str, end-str-1, "%s %c:%g", pre, c-xindex+'x', sv->v[i].f);
	} else if(c >= 0 && c < MAXVAL && (vd = &st->vdesc[st->curdata][c])->name[0] != '\0') {
	    str += snprintf(str, end-str-1, "%s %s:%g", pre, vd->name, sv->v[i].f);
	} else {
	    str += snprintf(str, end-str-1, "%s %d:%g", pre, c, sv->v[i].f);
	}
	pre = ",";
    }
    *str = '\0';
    return str;
}


static int warp_parse_args( struct dyndata *dd, struct stuff *st, int argc, char **argv )
{
  struct warpstuff *ws;
  char *op;
  if(argc < 1 || 0!=strcmp(argv[0], "warp"))
    return 0;
  if(dd == NULL || (ws = (struct warpstuff *)dd->data) == NULL) {
    msg("No warp enabled");
    return 1;
  }

  if(argc>1) {
      if(!strcmp(argv[1], "lock")) {
	st->dyn.enabled = 1;
	warp_invalidate( st, ws );	/* recompute just in case */
	ws->locked = 1;

      } else if(!strcmp(argv[1], "off")) {
	st->dyn.enabled = -1;	/* defined, but not active */
	warp_invalidate( st, ws );	/* force redisplay */

      } else if((!strcmp(argv[1], "unlock") || !strcmp(argv[1], "on"))) {
	st->dyn.enabled = 1;
	warp_invalidate( st, ws );	/* force recomputing */
	ws->locked = 0;
      }
  }

  op = ws->locked ? "locked " : st->dyn.enabled<0 ? "off " : "";

  switch(ws->style) {
     case EXTRAPOLATE:
	msg("warp %stfrac %g at time %g  extrapolate degree %d using fields %d..%d",
	    op,
	    ws->tfrac, ws->realtime,
	    ws->degree, ws->coef0, ws->coef0 + ws->degree*3 - 1);
	break;
    case DIFFROT:
	msg("warp %stfrac %g at time %g  fixp %g %g %g rfixd %g fixsys %d rcored %g",
	    op,
	    ws->tfrac, ws->realtime,
	    ws->fixedp[0],ws->fixedp[1],ws->fixedp[2], ws->fixedrdisk, ws->fixed_coordsys, ws->rcoredisk);
	break;
    case PROJECT: {
	    char str[200], *cp;
	    sprintf(str, "warp %s", op);
	    cp = str+strlen(str);

	    cp = projvec2str( st, cp, &str[sizeof(str)], " -wx", &ws->sproj[0] );
	    cp = projvec2str( st, cp, &str[sizeof(str)], " -wy", &ws->sproj[1] );
	    cp = projvec2str( st, cp, &str[sizeof(str)], " -wz", &ws->sproj[2] );
	    cp = projvec2str( st, cp, &str[sizeof(str)], " -ref", &ws->sref );
	    if(ws->sadd.len > 0) {
		cp += snprintf( cp, &str[sizeof(str)] - cp, " -add %g,%g,%g",
			ws->sadd.v[0].f, ws->sadd.v[1].f, ws->sadd.v[2].f);
	    }
	    msg(str);
        }
	break;
  }
  return 1;
}

int warp_get_trange( struct dyndata *dd, struct stuff *st, double *tminp, double *tmaxp, int ready )
{
  *tminp = -10000;
  *tmaxp = 10000;
  return 1;
}

static void scrapsl( struct specklist **slp ) {
  struct specklist *sl, *slnext;
  if(slp == NULL) return;
  sl = *slp;
  *slp = NULL;
  for( ; sl; sl = slnext) {
    slnext = sl->next;
    if(sl->specks) Free(sl->specks);
    Free(sl);
  }
}


int warp_read( struct stuff **stp, int argc, char *argv[], char *fname, void *etc ) {
    struct stuff *st = *stp;
    struct warpstuff *ws;

    if(argc < 1 || strcmp(argv[0], "warp") != 0)
	return 0;
    st->clk->continuous = 1;

    if(st->dyn.ctlcmd == warp_parse_args) {
	ws = (struct warpstuff *)st->dyn.data;
    } else {
	ws = NULL;
    }
    ws = warp_setup( st, ws, argc, argv );
    if(ws == NULL)
	return 0;

    memset(&st->dyn, 0, sizeof(st->dyn));
    st->dyn.data = ws;
    st->dyn.getspecks = warp_get_parti;
    st->dyn.trange = warp_get_trange;
    st->dyn.ctlcmd = warp_parse_args;
    st->dyn.draw = NULL;
    st->dyn.free = NULL;
    st->dyn.enabled = 1;
    warp_invalidate( st, ws );
    return 1;
}

void warp_init() {
    parti_add_reader( warp_read, "warp", NULL );
}

struct specklist *
warp_get_parti( struct dyndata *dd, struct stuff *st, double realtime )
{
  struct warpstuff *ws = (struct warpstuff *)dd->data;
  int ctx = get_dsp_context();
  struct speckcache *sc = &ws->slc[ctx];
  struct specklist *sl, *osl, **slp;
  static int once = 1;

#if unix
  if(once-- == 0 && getenv("DBG")) {
    fprintf(stderr, "warp_get_parti:   dbx -p %d\n", getpid());
    sleep(atoi(getenv("DBG")));
  }
#endif

  if(ws->locked && ws->valid && sc != NULL)
    return sc->sl;

  setup_coords( st, ws );	/* bind coordsys-related stuff now */

  ws->realtime = realtime;
  ws->tfrac = warpfrac(ws, realtime * ws->tunit);
  if(ws->has_zerotime)
    ws->tfrac -= warpfrac(ws, ws->zerotime);
  if(ws->has_trange && ws->period0 != 0)
    ws->tfrac *= (ws->tto - ws->tfrom) / ws->period0;

  if(ws->has_o2d && !ws->has_d2o) {
    deucinv( ws->d2o, ws->o2d );
    ws->has_d2o = -1;
  } else if(!ws->has_o2d && ws->has_d2o) {
    deucinv( ws->o2d, ws->d2o );
    ws->has_o2d = -1;
  }


  if(getenv("WDBG")) printf("%g %g %g # rt frac frac(rt)\n", realtime, ws->tfrac, warpfrac(ws, realtime*ws->tunit));

  if(sc->sl != NULL && sc->tfrac == ws->tfrac)
    return sc->sl;

  osl = specks_timespecks( st, 0, 0 );
  slp = &sc->sl;
  for(sl = *slp; osl != NULL; osl = osl->next, slp = &sl->next, sl = *slp) {
    if(sl == NULL || sl->speckseq != osl->speckseq) {
	scrapsl(slp);
	sl = NewN( struct specklist, 1 );
	*sl = *osl;
	sl->specks = NULL;
	sl->next = NULL;
	if(osl->specks) {
	    int len = osl->bytesperspeck * osl->nspecks;
	    sl->specks = (struct speck *)NewN( char, len );
	    memcpy( sl->specks, osl->specks, len );
	}
	*slp = sl;
    }
    warpspecks( ws, st, osl, sl );
  }

  sc->tfrac = ws->tfrac;
  return sc->sl;
}

#else /*STANDALONE*/

#include <stdarg.h>
#include "stardef.h"

int msg( CONST char *fmt, ... ) {
  char str[10240];

  va_list args;
  va_start(args, fmt);
  vsprintf(str, fmt, args);
  va_end(args);

  return fprintf(stderr, "%s\n", str);
}
int parti_idof( struct stuff *stjunk ) {
  return 1;
}

void parti_geto2w( struct stuff *stjunk, int objid, Matrix *o2w ) {
  int k;
  char *tfm = getenv("tfm");
  Matrix T;
  Point xyz;
  float aer[3], scl;
  if(tfm == NULL) {
    msg("warpsdb: need object-to-world transform, but no \"tfm\" env variable set?");
    exit(1);
  }
  k = sscanf(tfm,
"%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%f%*c%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]);

  scl = 1;

  switch(k) {
  case 1:
    scl = T.m[0];
    mscaling( o2w, scl,scl,scl );
    break;

  case 7:
    scl = T.m[6];  /* and fall into... */
  case 6:
    xyz.x[0] = T.m[0]; xyz.x[1] = T.m[1]; xyz.x[2] = T.m[2];
    aer[1] = T.m[3];   aer[0] = T.m[4];   aer[2] = T.m[5];
    xyzaer2tfm( &T, &xyz, aer );
    if(scl == 1) {
	*o2w = T;
    } else {
	Matrix Ts;
	mscaling( &Ts, scl,scl,scl );
	mmmul( o2w, &Ts, &T );
    }
    break;
  case 16:
    *o2w = T;
    break;
  default:
    msg("warpsdb: \"tfm\"=%s; expected 1 or 6 or 7 or 16 numbers", tfm);
    exit(1);
  }
}

void dwindup( struct warpstuff *ws, float *xyzout, float *dxyzout, CONST float *xyz )
{
  /*
   * v(r) =  (vmax/rcore)*r  r in 0..rcore
   *         vmax             r > rcore
   * period(r) = 2pi/(vmax/rcore)  r<rcore  [Call this T0.]
   *             2pi/(vmax/r) = (r/rcore)*T0
   * theta(t/T0,r) = (t/T0)*T0*(r < rcore ? 1 : rcore/r)
   * So rotate in the XZ plane about (0,0,0) by theta(t/T0,r)
   */
   float x = xyz[0];
   float z = xyz[2];
   float r = hypot( x, z );
   float omega = ( (r<=ws->rcoredisk) ? 1
		: (r>=ws->routercoredisk) ? ws->routercoredisk/r
		: slerp( 1 + (r-ws->routercoredisk)/(ws->routercoredisk-ws->rcoredisk),
			1, ws->routercoredisk/r )
		) - ws->fixomega;
 
   float theta = omega * ws->tfrac + ws->rigidrot;
   float dtheta = omega * ws->dtfrac * 2*M_PI;
   float s = fastsin2pi( theta );
   float c = fastsin2pi( theta + 0.25 );

   xyzout[0] = x*c - z*s;
   xyzout[1] = xyz[1];
   xyzout[2] = x*s + z*c;

   dxyzout[0] = -dtheta * (x*s + z*c);
   dxyzout[1] = 0;
   dxyzout[2] = dtheta * (x*c - z*s);
}

void dwindup_o2d( struct warpstuff *ws, float *xyzout, float *dxyzout, CONST float *xyz )
{
    float v[3], w[3], dw[3];
    int i;

    for(i = 0; i < 3; i++)
	v[i] = xyz[0]*ws->o2d[0*4+i] + xyz[1]*ws->o2d[1*4+i]
	     + xyz[2]*ws->o2d[2*4+i] + ws->o2d[3*4+i];
    dwindup( ws, w, dw, v );
    for(i = 0; i < 3; i++) {
	xyzout[i] = w[0]*ws->d2o[0*4+i] + w[1]*ws->d2o[1*4+i]
		  + w[2]*ws->d2o[2*4+i] + ws->d2o[3*4+i];
	dxyzout[i] = dw[0]*ws->d2o[0*4+i] + dw[1]*ws->d2o[1*4+i]
		   + dw[2]*ws->d2o[2*4+i];
    }
}

void dextrap( struct warpstuff *ws, float *xyzout, float *dxyzout,
		CONST float *xyz, CONST struct speck *sp, int deg )
{
    float *coefval = &sp->val[ws->coef0];
    if(deg == 1) {
	xyzout[0] = xyz[0] + coefval[0]*ws->tfrac;
	xyzout[1] = xyz[1] + coefval[1]*ws->tfrac;
	xyzout[2] = xyz[2] + coefval[2]*ws->tfrac;
	dxyzout[0] = coefval[0]*ws->dtfrac;
	dxyzout[1] = coefval[1]*ws->dtfrac;
	dxyzout[2] = coefval[2]*ws->dtfrac;
    } else {
	int i;
	float t = ws->tfrac;
	float tprime = ws->tfrac + ws->dtfrac;
	xyzout[0] = xyz[0];
	xyzout[1] = xyz[1];
	xyzout[2] = xyz[2];
	dxyzout[0] = dxyzout[1] = dxyzout[2] = 0;
	for(i = 0; i < deg; i++, coefval += 3) {
	    xyzout[0] += coefval[0]*t;
	    xyzout[1] += coefval[1]*t;
	    xyzout[2] += coefval[2]*t;
	    dxyzout[0] += coefval[0]*(tprime-t);
	    dxyzout[1] += coefval[1]*(tprime-t);
	    dxyzout[2] += coefval[2]*(tprime-t);
	    t *= ws->tfrac;
	    tprime *= (ws->tfrac + ws->dtfrac);
	}
    }
}

void dextrap_o2d( struct warpstuff *ws, float *xyzout, float *dxyzout,
		CONST struct speck *sp, int deg )
{
    float v[3], w[3], dw[3];
    int i;
    for(i = 0; i < 3; i++)
	v[i] = sp->p.x[0]*ws->o2d[0*4+i] + sp->p.x[1]*ws->o2d[1*4+i]
	     + sp->p.x[2]*ws->o2d[2*4+i] + ws->o2d[3*4+i];
    dextrap( ws, w, dw, v, sp, deg );
    for(i = 0; i < 3; i++) {
	xyzout[i] = w[0]*ws->d2o[0*4+i] + w[1]*ws->d2o[1*4+i]
		  + w[2]*ws->d2o[2*4+i] + ws->d2o[3*4+i];
	dxyzout[i] = dw[0]*ws->d2o[0*4+i] + dw[1]*ws->d2o[1*4+i]
		   + dw[2]*ws->d2o[2*4+i];
    }
}

static void swabsdb( db_star *sp ) {
    int i;
    for(i = 0; i < 9; i++)
	((int *)sp)[i] = htonl(((int *)sp)[i]);	/* x,y,z...,num */
    sp->color = htons(sp->color);
}

void warpsdb( struct warpstuff *ws, FILE *inf, FILE *outf ) {
    static int one = 1;
    int needswab = (*(char *)&one != 0);
    int nstars = 0;
    db_star star;

    if(ws->has_o2d && !ws->has_d2o) {
	deucinv( ws->d2o, ws->o2d );
	ws->has_d2o = -1;
    } else if(!ws->has_o2d && ws->has_d2o) {
	deucinv( ws->o2d, ws->d2o );
	ws->has_o2d = -1;
    }

    while(fread(&star, sizeof(star), 1, inf) > 0) {
	float xyz[3];
	nstars++;
	if(needswab)
	    swabsdb(&star);
	memcpy(xyz, &star.x, 3*sizeof(float));
	if(ws->has_o2d)
	    dwindup_o2d( ws, &star.x, &star.dx, xyz );
	else
	    dwindup( ws, &star.x, &star.dx, xyz );
	if(needswab)
	    swabsdb(&star);
	if(fwrite(&star, sizeof(star), 1, outf) <= 0) {
	    msg("Error writing star #%d", nstars);
	    break;
	}
    }
}

int main(int argc, char *argv[]) {
    struct warpstuff *ws;
    char *infname = NULL;
    FILE *inf = stdin;

#define BIGBUFSIZE 2097152
    char *obuf = (char *)malloc(BIGBUFSIZE);
    char *ibuf = (char *)malloc(BIGBUFSIZE);

    if(argc > 1 && 0==strcmp(&argv[argc-1][strlen(argv[argc-1])-4], ".sdb")) {
	infname = argv[argc-1];
	argc--;
	inf = fopen(infname, "rb");
	if(inf == NULL) {
	    msg("%s: %s: cannot open input", argv[0], infname);
	    exit(1);
	}
    }
    setvbuf( inf,    ibuf, _IOFBF, BIGBUFSIZE );
    setvbuf( stdout, obuf, _IOFBF, BIGBUFSIZE );

    ws = warp_setup( st, NULL, argc, argv );
    if(ws == NULL)
	exit(1);	/* warp_setup() must have already printed a message */

    setup_coords( NULL, ws );
    if(ws->has_fixed && getenv("WARPDBG")) {
	fprintf(stderr, "warp rcoredisk %g fixrdisk %g fixomega %g\n", ws->rcoredisk, ws->fixedrdisk, ws->fixomega);
    }

    warpsdb( ws, inf, stdout );
    return 0;
}

#endif /*!STANDALONE*/

#endif /*USE_WARP*/