/*
	The MagTape Widget Methods.

	11-feb-2025	Created by mk@lemo.dk
*/

/*
#undef DEBUG
*/

#include <GL/glx.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <malloc.h>
#include <time.h>
#include <sys/fcntl.h>
#include <Xm/XmP.h>

#include "MagTape.h"
#include "MagTapeP.h"
#include "spole5.xpm"


#define dtor(x) ( (x)/180.0*M_PI )
#define rtod(x) ( (x)*180.0/M_PI )

extern MagTapeApplicationData magTape_applicationdata;

extern void bbox(double ,double *,double *,double *,double *,char *);
extern void stroke(double ,double ,double ,double ,char *);

/*
Forward declarations */
static void Resize();
static void XsmagTapeInsertValues();
static void XsmagTapeInsertDefaultValues();
static void updateit(XsMagTapeWidget, int);
GLvoid DrawGLScene(XsMagTapeWidget,int);
static void startTimer(XsMagTapeWidget);

#define offset(field) XtOffset(XsMagTapeWidget, magTape.field)

XtResource magTape_resources[]=
{

  {XtNbackgroundColor, XtCColor, XtRInt, sizeof(int),
   offset(backgroundcolor), XtRString, "0"},

  {XtNtapeLabels, XtCTapeLabels, XtRPointer, sizeof(caddr_t),
   offset(ptapelabels), XtRPointer, NULL},

  {XtNnDrives, XtCNDrives, XtRInt, sizeof(int),
   offset(ndrives), XtRString, "1"},

  {XtNdone, XtCCallback, XtRCallback, sizeof(caddr_t),
   offset(done), XtRCallback, NULL},

  /* OpenGL stuff */

  {XtNvisualInfo, XtCVisualInfo, XtRVisualInfo, sizeof (XVisualInfo *),
   offset(visualInfo), XtRImmediate, (XtPointer) NULL}
};

#undef offset

static void done_magTape(XsMagTapeWidget,int);
static void InsertChar(XsMagTapeWidget,XEvent *);
	    

static XtActionsRec actionsList[]=
{
  {"done", (XtActionProc) done_magTape},
  {"insert-char", (XtActionProc) InsertChar}
};

static void Initialize(), Realize(), Destroy(), Resize(), Redisplay();
static Boolean SetValues();

static char defaultTranslations[] =
	"~c <Key>:	insert-char()";

XsMagTapeClassRec XsmagTapeClassRec =
{
  /*    Core class part */
  {
    (WidgetClass) &widgetClassRec,      /* superclass */
    "MagTape",                              /* class_name */
    sizeof(XsMagTapeRec),                   /* widget_size */
    NULL,                               /* class_initialze */
    NULL,                               /* class_part_initialize */
    FALSE,                              /* class_inited */
    Initialize,                         /* initialize */
    NULL,                               /* initialize_hook */
    Realize,                            /* realize */
    actionsList,                        /* actions */
    XtNumber(actionsList),              /* num_actions */
    magTape_resources,                      /* resources */
    XtNumber(magTape_resources),            /* num_resources */
    NULLQUARK,                          /* xrm_class */
    TRUE,                               /* compress_motion */
    XtExposeCompressMaximal,            /* compress_exposure */
    TRUE,                               /* compress_enterleave */
    TRUE,                               /* visibility_interest */
    Destroy,                            /* destroy */
    Resize,                             /* resize */
    Redisplay,                          /* expose */
    SetValues,                          /* set_values */
    NULL,                               /* set_values_hook */
    XtInheritSetValuesAlmost,           /* set_values_almost */
    NULL,                               /* get_values_hook */
    NULL,                               /* accept_focus */
    XtVersion,                          /* version */
    NULL,                               /* callback private */
    defaultTranslations,                /* tm_table */
    NULL,                               /* query_geometry */

    NULL,                               /* display_accelerator */
    NULL,                               /* extension */
  },
  {          /* XmPrimitive        */
     XmInheritBorderHighlight,       /* border_highlight      */
     XmInheritBorderUnhighlight,     /* border_unhighlight    */
     XmInheritTranslations,          /* translations          */
     NULL,                           /* arm_and_activate      */
     NULL,                           /* synresources          */
     0,                              /* num syn_resources     */
     NULL,                           /* extension             */
  },
  /*    MagTape class fields */

  {
    0,
  }
};

WidgetClass XsmagTapeWidgetClass = (WidgetClass) &XsmagTapeClassRec;

int ImageLoad(const char **xpm, Image *image) {
    unsigned long i,r,c,size;                    // standard counter.
    unsigned int ncolors,charsPerPixel,index,*p,*clookup;

    sscanf(xpm[0], "%u %u %u %u",&image->sizeX,&image->sizeY,&ncolors,&charsPerPixel);

    clookup=(unsigned int *) malloc(sizeof(*clookup)*256*256);

    for(i=0;i<65536;i++) clookup[i]=0;
    for(i=1;i<=ncolors;i++)
    {
      index=xpm[i][0]+xpm[i][1]*256;
      if(xpm[i][3]=='c')
      {
	if(xpm[i][5]=='#')
	{
	  sscanf(xpm[i]+6, "%X", &clookup[index]);
	  clookup[index] |= 0xFF000000;
	}
	else if(xpm[i][5]=='N')
	{
	  clookup[index]=0x00FFFFFF;
	}
	else if(xpm[i][5]=='b')
	{
	  clookup[index]=0xFF000000;
	}
      }
    }
    size = image->sizeX * image->sizeY * 4;
    image->data = (char *) malloc(size);
    p = (unsigned int *) (image->data);
    for(r=0;r<image->sizeY;r++)
    {
      for(c=0;c<image->sizeX;c++)
      {
	index=xpm[ncolors+1+r][c*2] + xpm[i+r][c*2+1]*256;
	p[r*image->sizeX+c]=clookup[index];
      }
    }
#ifdef uakk
    p = (unsigned int *) image->data;
    for(r=0;r<image->sizeY;r++)
      for(c=0;c<image->sizeX;c++)
      {
	printf("%4ld %4ld %8.8X\n",r,c,(*p));
	p++;
      }
#endif
    free((char *) clookup);
    
    return 1;
}


static double seconds(XsMagTapeWidget w)
{
  double td;
  long tn;
  clock_gettime(CLOCK_MONOTONIC, &(w->magTape.time1));
  tn=w->magTape.time1.tv_nsec - w->magTape.time0.tv_nsec;
  td=w->magTape.time1.tv_sec - w->magTape.time0.tv_sec;
  if(tn<0)
  {
    tn += 1000000000L;
    td -= 1.0;
  }
  td += ((double)tn)*1e-9;
  return(td);
}

static int QUAREQ2(double A, double B, double C, double *x1, double *x2)
{
  double D;
  int ret=0;
  if(A==0.0)
  {
    if(B==0.0)
      ret=1;
    else
    {
      *x1 = -C/A;
      *x2 = *x1;
    }
  }
  else
  {
    D=SQR(B)-4.0*A*C;
    if(D<0.0)
      ret=1;
    else
    {
      *x1 = (-B - ((B<0.0)?-1.0:1.0)*sqrt(D))/2.0/A;
      *x2 = C/A/(*x1);
    }
  }
  return(ret);
}

static double radius2Length(XsMagTapeWidget w,double r)
{
  return((M_PI/w->magTape.thickness)*SQR(r)+M_PI*r-SQR(w->magTape.radius0)*M_PI/w->magTape.thickness);
}

static double length2Thickness(XsMagTapeWidget w,double l, double r)
{
  return(M_PI*(SQR(r)-SQR(w->magTape.radius0))/(l-M_PI*r));
}

static double length2Radius(XsMagTapeWidget w,double l)
{
  double A,B,C,x1,x2;
  A=M_PI/w->magTape.thickness;
  B=M_PI;
  C=-(SQR(w->magTape.radius0)*M_PI/w->magTape.thickness)-l;
  QUAREQ2(A, B, C, &x1, &x2);
  return(x2);
}

static double rotation2Length(XsMagTapeWidget w,double r)
{
  return(M_PI*w->magTape.thickness*SQR(r)+(2.0*M_PI*w->magTape.radius0)*r);
}

static double length2Rotation(XsMagTapeWidget w,double l)
{
  double A,B,C,x1,x2;
  A=M_PI*w->magTape.thickness;
  B=2.0*M_PI*w->magTape.radius0;
  C=-l;
  QUAREQ2(A, B, C, &x1, &x2);
  return(x2);
}


static void disk(GLdouble x0, GLdouble y0, GLdouble z0,GLdouble r1, GLdouble r2)
{
  GLdouble x1,y1,x2,y2,v,lastx1,lasty1,lastx2,lasty2;

  lastx1=x0+r1;
  lasty1=y0;
  lastx2=x0+r2;
  lasty2=y0;
  for(v=5;v<=360;v+=5)
  {
    x1=x0+r1*cos(v/180.0*M_PI);
    y1=y0+r1*sin(v/180.0*M_PI);
    x2=x0+r2*cos(v/180.0*M_PI);
    y2=y0+r2*sin(v/180.0*M_PI);
    glVertex3d(lastx1,lasty1,z0);
    glVertex3d(lastx2,lasty2,z0);
    glVertex3d(x1,y1,z0);
    glVertex3d(lastx2,lasty2,z0);
    glVertex3d(x2,y2,z0);
    glVertex3d(x1,y1,z0);
    lastx1=x1;
    lasty1=y1;
    lastx2=x2;
    lasty2=y2;
  }
}

static void circle(GLdouble x0, GLdouble y0, GLdouble r, GLdouble v0, GLdouble v1)
{
  GLdouble lastx,lasty,x,y,v;
  int i,n;

  n=r*2;
  lastx=x0+r*cos(v0/180.0*M_PI);
  lasty=y0-r*sin(v0/180.0*M_PI);
  for(i=1;i<=n;i++)
  {
    v=v0+(v1-v0)*((double)i)/((double) n);
    x=x0+r*cos(v/180.0*M_PI);
    y=y0-r*sin(v/180.0*M_PI);
    glVertex3d(lastx,lasty,0.0);
    glVertex3d(x,y,0.0);
    lastx=x;
    lasty=y;
  }
}


    
// Load Bitmaps And Convert To Textures
void LoadGLTextures(XsMagTapeWidget w) {	
    // Load Texture

    // allocate space for texture
    w->magTape.image1 = (Image *) malloc(sizeof(Image));
    if (w->magTape.image1 == NULL) {
	printf("Error allocating space for image");
	exit(0);
    }

    if (!ImageLoad(spole5, w->magTape.image1)) {
	exit(1);
    }        

    // Create Texture	
    glGenTextures(1, &(w->magTape.texture[0]));
//  glBindTexture(GL_TEXTURE_2D, w->magTape.texture[0]);   // 2d texture (x and y size)

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // scale linearly when image bigger than texture
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // scale linearly when image smalled than texture

    // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, 
    // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself.
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w->magTape.image1->sizeX, w->magTape.image1->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, w->magTape.image1->data);
};

static void intersectionLineCircle(double a1, double c1,
                                   double x1, double y1, double r1,
				   double *xi1, double *yi1,
				   double *xi2, double *yi2)
{
  double A,B,C,d,tmp;
  A=(1.0+SQR(a1));
  B=(-2.0*x1+2.0*a1*(y1+c1));
  C=SQR(x1)+SQR(y1+c1)-SQR(r1);
  d=SQR(B)-4.0*A*C;
  if(d<0)
  {
    fprintf(stderr, "no intersection\n");
    exit(-1);
  }
  *xi1 = (-B+sqrt(d))/2.0/A;
  *xi2 = (-B-sqrt(d))/2.0/A;
  if((*xi1) > (*xi2))
  {
    tmp=*xi1;
    *xi1=*xi2;
    *xi2=tmp;
  }
  *yi1 = -c1-a1*(*xi1);
  *yi2 = -c1-a1*(*xi2);
}

static void intersectionLines(double a1, double c1,
                              double a2, double c2,
			      double *x, double *y)
{
  *x = (c1-c2)/(a2-a1);
  *y = -c1-a1*(*x);
}

static void intersectionCircles(double x1, double y1, double r1,
                                double x2, double y2, double r2,
				double *xi1, double *yi1,
				double *xi2, double *yi2)
{
  double R,tmp;
  /* https://math.stackexchange.com/questions/256100/how-can-i-find-the-points-at-which-two-circles-intersect */
  R=sqrt(SQR(x1-x2)+SQR(y1-y2));
  *xi1 = (x1+x2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(x2-x1)
        +0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(y2-y1);
  *xi2 = (x1+x2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(x2-x1)
        -0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(y2-y1);
  *yi1 = (y1+y2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(y2-y1)
        +0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(x1-x2);
  *yi2 = (y1+y2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(y2-y1)
        -0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(x1-x2);
  if((*xi1)>(*xi2))
  {
    tmp=(*xi1); *xi1 = (*xi2); *xi2=tmp;
    tmp=(*yi1); *yi1 = (*yi2); *yi2=tmp;
  }
}

static void points2Line(double x1, double y1,
                        double x2, double y2,
			double *a, double *c)
{
  *a = (y1-y2)/(x2-x1);
  *c = -(*a)*x1-y1;
}

static void twoCirclesTangent(double x1, double y1, double r1,
                              double x2, double y2, double r2,
			      int transverse,
			      double *xi11, double *yi11,
			      double *xi12, double *yi12,
			      double *xi21, double *yi21,
			      double *xi22, double *yi22)
{
  /* https://math.stackexchange.com/questions/1662110/common-tangent-to-two-circles */
  double a,c,xr,yr,ca,cc,cx1,cy1,cx2,cy2,ca2,cc2,x3,y3,r3,angle;
  points2Line(x1,y1,x2,y2,&ca,&cc);
  angle=atan2(y2-y1,x2-x1)+M_PI_2;
  cx1=x1+r1*cos(angle);
  cy1=y1+r1*sin(angle);
  cx2=x2+r2*cos(angle+(transverse?M_PI:0));
  cy2=y2+r2*sin(angle+(transverse?M_PI:0));
  points2Line(cx1,cy1,cx2,cy2,&ca2,&cc2);
  intersectionLines(ca,cc,ca2,cc2,&xr,&yr);
  x3=(x2+xr)/2.0;
  y3=(y2+yr)/2.0;
  r3=sqrt(SQR(xr-x2)+SQR(yr-y2))/2.0;
  intersectionCircles(x2,y2,r2,x3,y3,r3,xi21,yi21,xi22,yi22);
  x3=(x1+xr)/2.0;
  y3=(y1+yr)/2.0;
  r3=sqrt(SQR(xr-x1)+SQR(yr-y1))/2.0;
  intersectionCircles(x1,y1,r1,x3,y3,r3,xi11,yi11,xi12,yi12);
}

GLfloat LightAmbient[]  = {0.5, 0.5, 0.5, 1.0}; 
GLfloat LightDiffuse[]  = {1.0, 1.0, 1.0, 1.0}; 
GLfloat LightPosition[] = {0.0, 0.0, 2.0, 1.0};

static void setTimerRunning(XsMagTapeWidget w)
{
  int unitNo;
  w->magTape.timerRunning=0;
  for(unitNo=0; unitNo<w->magTape.ndrives; unitNo++)
  {
    if(w->magTape.status[unitNo] != TAPE_IDLE) w->magTape.timerRunning=1;
    if(fabs(w->magTape.inSpeed[unitNo])>IDLE_SPEED) w->magTape.timerRunning=1;
    if(fabs(w->magTape.capstanSpeed[unitNo])>IDLE_SPEED) w->magTape.timerRunning=1;
    if(fabs(w->magTape.outSpeed[unitNo])>IDLE_SPEED) w->magTape.timerRunning=1;
  }
  if(!w->magTape.timerRunning)
  {
    for(unitNo=0; unitNo<w->magTape.ndrives; unitNo++)
    {
      w->magTape.inSpeed[unitNo]=0.0;
      w->magTape.capstanSpeed[unitNo]=0.0;
      w->magTape.outSpeed[unitNo]=0.0;
    }
  }
#ifdef DEBUG
  printf("setTimerRunning: %d\n",w->magTape.timerRunning);
#endif
}

/* The main drawing function. */

void timercb(XtPointer client_data, XtIntervalId *timer_id)
{
  double total;
  int unitNo;
  XsMagTapeWidget w = (XsMagTapeWidget) client_data;
  w->magTape.secs=seconds(w);
  w->magTape.dsecs2=w->magTape.secs-w->magTape.lastsecs;
  w->magTape.lastsecs=w->magTape.secs;
  w->magTape.dsecs=w->magTape.dsecs2;
#ifdef DEBUG
  printf("kilroy2 dsecs2: %g\n", w->magTape.dsecs2);
#endif
  fflush(stdout);
  for(unitNo=0; unitNo<w->magTape.ndrives; unitNo++)
  {
    updateit(w,unitNo);
  }
  Redisplay(w,NULL,NULL);
  setTimerRunning(w);
#ifdef DEBUG
  printf("start timer: %d\n",w->magTape.timerRunning);
#endif
  if(w->magTape.timerRunning)startTimer(w);
}

static void startTimer(XsMagTapeWidget w)
{
#ifdef DEBUG
  printf("kilroy1:\n");
#endif
  XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) w), 10, timercb, (XtPointer) w);
}

static void updateit(XsMagTapeWidget w, int unitNo)
{
  double total;
    switch(w->magTape.status[unitNo])
    {
      case TAPE_IDLE:
	break;

      case FORWARD_ACCELERATE:
	if(w->magTape.capstanSpeed[unitNo]>READSPEED)
	{
	  w->magTape.capstanAcceleration[unitNo]=0.0;
	  w->magTape.capstanSpeed[unitNo]=READSPEED;
	  w->magTape.status[unitNo]=FORWARD_KEEP;
	}
	break;
      case FORWARD_KEEP:
	if((w->magTape.taperead[unitNo]-w->magTape.readstart[unitNo]+GAPLEN)>=w->magTape.tapeappetite[unitNo])
	{
	  w->magTape.capstanAcceleration[unitNo]=-CAPSTANACCELERATION;
	  w->magTape.status[unitNo]=FORWARD_DECELERATE;
	}
	break;
      case FORWARD_DECELERATE:
	if(w->magTape.capstanSpeed[unitNo]<0.01)
	{
	  w->magTape.capstanSpeed[unitNo]=0;
	  w->magTape.capstanAcceleration[unitNo]=0;
	  w->magTape.tapeadjust[unitNo]=w->magTape.readstart[unitNo]+w->magTape.tapeappetite[unitNo]-w->magTape.taperead[unitNo];
#ifdef DEBUG
	  printf("%d: tapeadjust: %.8f\n",unitNo, w->magTape.tapeadjust[unitNo]);
#endif
	  w->magTape.taperead[unitNo]+=w->magTape.tapeadjust[unitNo];
	  w->magTape.inTape[unitNo]-=w->magTape.tapeadjust[unitNo];
	  w->magTape.outTape[unitNo]+=w->magTape.tapeadjust[unitNo];
	  w->magTape.status[unitNo]=TAPE_IDLE;
	  done_magTape(w,unitNo);
//	  goto out;
	}
	break;

      case REVERSE_ACCELERATE:
	if(w->magTape.capstanSpeed[unitNo]<(-READSPEED))
	{
	  w->magTape.capstanAcceleration[unitNo]=0.0;
	  w->magTape.capstanSpeed[unitNo]=-READSPEED;
	  w->magTape.status[unitNo]=REVERSE_KEEP;
	}
	break;
      case REVERSE_KEEP:
	if((w->magTape.readstart[unitNo]-w->magTape.taperead[unitNo]+GAPLEN)>=w->magTape.tapeappetite[unitNo])
	{
	  w->magTape.capstanAcceleration[unitNo]=CAPSTANACCELERATION;
	  w->magTape.status[unitNo]=REVERSE_DECELERATE;
	}
	break;
      case REVERSE_DECELERATE:
	if(w->magTape.capstanSpeed[unitNo]>-0.01)
	{
	  w->magTape.capstanSpeed[unitNo]=0;
	  w->magTape.capstanAcceleration[unitNo]=0;
	  w->magTape.tapeadjust[unitNo]=w->magTape.taperead[unitNo]-w->magTape.readstart[unitNo]+w->magTape.tapeappetite[unitNo];
#ifdef DEBUG
	  printf("%d: tapeadjust: %.8f\n",unitNo, w->magTape.tapeadjust[unitNo]);
#endif
	  w->magTape.taperead[unitNo]-=w->magTape.tapeadjust[unitNo];
	  w->magTape.inTape[unitNo]+=w->magTape.tapeadjust[unitNo];
	  w->magTape.outTape[unitNo]-=w->magTape.tapeadjust[unitNo];
	  w->magTape.status[unitNo]=TAPE_IDLE;
	  w->magTape.tapeadjust[unitNo]=w->magTape.taperead[unitNo]-w->magTape.readstart[unitNo]-w->magTape.tapeappetite[unitNo];
	  done_magTape(w,unitNo);
//	  goto out;
	}
	break;

      case REWIND_ACCELERATE:
	if(w->magTape.capstanSpeed[unitNo]<(-REWINDSPEED))
	{
	  w->magTape.capstanAcceleration[unitNo]=0.0;
	  w->magTape.capstanSpeed[unitNo]=-REWINDSPEED;
	  w->magTape.status[unitNo]=REWIND_KEEP;
	}
	break;
      case REWIND_KEEP:
	if(w->magTape.OUTlength[unitNo]<GAPLEN)
	{
	  w->magTape.capstanAcceleration[unitNo]=CAPSTANACCELERATION;
	  w->magTape.status[unitNo]=REWIND_DECELERATE;
	}
	break;
      case REWIND_DECELERATE:
	if(w->magTape.capstanSpeed[unitNo]>-0.01)
	{
	  w->magTape.capstanSpeed[unitNo]=0;
	  w->magTape.capstanAcceleration[unitNo]=0;
	  w->magTape.OUTlength[unitNo]=0.0;
	  w->magTape.INlength[unitNo]=TAPELENGTH;
	  w->magTape.taperead[unitNo]=0;
	  w->magTape.status[unitNo]=TAPE_IDLE;
	  done_magTape(w,unitNo);
//	  goto out;
	}
	break;
    }
    w->magTape.INlength[unitNo] -= w->magTape.inSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.OUTlength[unitNo] -= w->magTape.outSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.inTape[unitNo] += w->magTape.inSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.outTape[unitNo] += w->magTape.outSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.inTape[unitNo] -= w->magTape.capstanSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.outTape[unitNo] += w->magTape.capstanSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.taperead[unitNo] += w->magTape.capstanSpeed[unitNo]*w->magTape.dsecs;
    w->magTape.inSpeed[unitNo] += w->magTape.inAcceleration[unitNo]*w->magTape.dsecs;
    w->magTape.outSpeed[unitNo] += w->magTape.outAcceleration[unitNo]*w->magTape.dsecs;
    w->magTape.capstanSpeed[unitNo] += w->magTape.capstanAcceleration[unitNo]*w->magTape.dsecs;
    
    w->magTape.inRatio[unitNo] = w->magTape.inTape[unitNo]/MAXCHAMPERTAPELENGTH - 0.5;
    w->magTape.outRatio[unitNo] = w->magTape.outTape[unitNo]/MAXCHAMPERTAPELENGTH - 0.5;

    w->magTape.inError[unitNo]=w->magTape.inSpeed[unitNo]-w->magTape.capstanSpeed[unitNo];
    w->magTape.outError[unitNo]=w->magTape.outSpeed[unitNo]+w->magTape.capstanSpeed[unitNo];
    w->magTape.inAcceleration[unitNo]= -w->magTape.inError[unitNo]*w->magTape.UAKK[unitNo];
    if(fabs(w->magTape.inRatio[unitNo])>0.2)w->magTape.inAcceleration[unitNo]-=w->magTape.inRatio[unitNo]*w->magTape.UAKK[unitNo];
    w->magTape.outAcceleration[unitNo]= -w->magTape.outError[unitNo]*w->magTape.UAKK[unitNo];
    if(fabs(w->magTape.outRatio[unitNo])>0.2)w->magTape.outAcceleration[unitNo]-=w->magTape.outRatio[unitNo]*w->magTape.UAKK[unitNo];
    total=w->magTape.INlength[unitNo]+w->magTape.OUTlength[unitNo]+w->magTape.inTape[unitNo]+w->magTape.outTape[unitNo];

#ifdef DEBUG2
    printf("%d: dsecs: %10.4f: INlength: %.4f capstanAcceleration: %.4f inTape: %.4f inSpeed: %.4f inAcceleration: %.4f\n",unitNo, w->magTape.dsecs,w->magTape.INlength[unitNo],w->magTape.capstanAcceleration[unitNo],w->magTape.inTape[unitNo],w->magTape.inSpeed[unitNo],w->magTape.inAcceleration[unitNo]);
#endif
#ifdef DEBUG
    printf("%d: inSpeed: %g capstanSpeed: %g outSpeed: %g\n",unitNo,w->magTape.inSpeed[unitNo],w->magTape.capstanSpeed[unitNo],w->magTape.outSpeed[unitNo]);
#endif
    if(w->magTape.inRatio[unitNo] < -0.5 || w->magTape.inRatio[unitNo] > 0.5 || w->magTape.outRatio[unitNo] < -0.5 || w->magTape.outRatio[unitNo] > 0.5)
    {
      printf("%d: inRatio: %g outRatio: %g\n",unitNo,w->magTape.inRatio[unitNo],w->magTape.outRatio[unitNo]);
    }

#ifdef DEBUG
  printf("%d: timercb done\n", unitNo);
#endif
}

static void Layout(XsMagTapeWidget w)
{
#ifdef DEBUG
  printf("Layout: ndrives: %d\n",w->magTape.ndrives);
#endif
  switch(w->magTape.ndrives)
  {
    case 1:
    case 2:
    case 3:
      w->magTape.nx=w->magTape.ndrives;
      w->magTape.ny=1;
      break;
    case 4:
      w->magTape.nx=2;
      w->magTape.ny=2;
      break;
    case 5:
    case 6:
      w->magTape.nx=3;
      w->magTape.ny=2;
      break;
  }
}

GLvoid DrawGLScene(XsMagTapeWidget w,int unitNo)
{
  char txt[100];
  double x,y;
  int width, height;
  int widthr, heightr;
  int ix, iy;
  GLdouble window_xmin, window_xmax, window_ymin, window_ymax;
  GLdouble xcenter, ycenter, aspect, xsize, ysize, fHeight, fWidth;
  double a1,c1,xi1,yi1,xi2,yi2,angle1,angle2;
  double xi11,yi11,xi12,yi12,xi21,yi21,xi22,yi22;
  double xcenter1,ycenter1,xcenter2,ycenter2,acenter,ccenter;
  double r1,angle3,x1,y1,x2,y2,x3,y3,s,sx,sy;
  double tapewidth=1.0;
  double textXmin, textXmax, textYmin, textYmax;

#ifdef DEBUG
  printf("%d: glXMakeCurrent 1\n", unitNo);
#endif
  Layout(w);
  glXMakeCurrent(XtDisplay(w), XtWindow(w), w->magTape.glx_context);
  glClearColor(1.0, 1.0, 1.0, 1.0);	// Clear The Background Color To White 
  glClearDepth(0.9);				// Enables Clearing Of The Depth Buffer
  glDepthFunc(GL_LESS);			// The Type Of Depth Test To Do
  glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
  glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

  glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glDisable(GL_BLEND);
  glEnable(GL_LINE_SMOOTH);
    
  glMatrixMode(GL_MODELVIEW);

  glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
  glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
  glEnable(GL_LIGHT1);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
#ifdef DEBUG
  printf("%d: width: %d height: %d nx: %d ny: %d\n", unitNo, w->core.width, w->core.height,
      w->magTape.nx, w->magTape.ny);
#endif

  ix=unitNo%w->magTape.nx;
  iy=unitNo/w->magTape.nx;
  iy=w->magTape.ny-1-iy;
  widthr = w->core.width/w->magTape.nx;
  heightr = w->core.height/w->magTape.ny;
 
  glViewport(ix*widthr, iy*heightr, widthr, heightr);
  w->magTape.window_aspect = ((GLdouble) widthr)/((GLdouble) heightr)*((GLdouble) YMAX)/((GLdouble) XMAX);
#ifdef DEBUG
  printf("%d: glViewport(%d, %d, %d, %d)\n",unitNo,ix*widthr, iy*heightr, widthr, heightr);
  printf("%d: window_aspect 1: %g\n",unitNo, w->magTape.window_aspect);
#endif
  glOrtho(0.0,((double)XMAX)*w->magTape.window_aspect,((double)YMAX),0.0,-1.0,1.0);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
  glLoadIdentity();				// Reset The View
  glBegin(GL_LINES);
  glColor4d(0.0,0.0,0.0,1.0);
  glLineWidth(1.0);
  /*
  glVertex3d(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0);
  glVertex3d(BottomReelX-BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0);

  glVertex3d(BottomReelX-BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0);
  glVertex3d(BottomReelX+BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0);

  glVertex3d(BottomReelX+BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0);
  glVertex3d(BottomReelX+BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0);

  glVertex3d(BottomReelX+BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0);
  glVertex3d(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0);
  */

  glVertex3d(1.0, 0.0, 0.0);
  glVertex3d(XMAX, 0.0, 0.0);

  glVertex3d(XMAX, 0.0, 0.0);
  glVertex3d(XMAX, YMAX, 0.0);

  glVertex3d(XMAX, YMAX, 0.0);
  glVertex3d(1.0, YMAX, 0.0);

  glVertex3d(1.0, YMAX, 0.0);
  glVertex3d(1.0, 0.0, 0.0);


  circle(TopReelX, TopReelY, TopReelOuterR, 0.0, 360.0);
  circle(TopReelX, TopReelY, TopReelInnerR, 0.0, 360.0);

#ifdef USECORNER
  circle(TopCornerX, TopCornerY, TopCornerOuterR, 0.0, 360.0);
  circle(TopCornerX, TopCornerY, TopCornerInnerR, 0.0, 360.0);
#endif

  circle(TopChamperInX, TopChamperInY, TopChamperInInnerR, 0.0, 360.0);

  points2Line(TopChamperOuter1X, TopChamperOuter1Y, TopChamperOuter2X, TopChamperOuter2Y,
              &a1, &c1);
  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInInnerR,
              &xi1,&yi1,&xi2,&yi2);

  glVertex3d(xi2,yi2,0.0);
  glVertex3d(TopChamperOuter2X,TopChamperOuter2Y,0.0);

  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);

  angle1=-atan2(yi2-TopChamperInY,xi2-TopChamperInX)*180.0/M_PI;
  points2Line(TopChamperOuter4X, TopChamperOuter4Y, TopChamperOuter1X, TopChamperOuter1Y,
              &a1,&c1);
  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);
  angle2=360.0-atan2(yi2-TopChamperInY,xi2-TopChamperInX)*180.0/M_PI;

//printf("Top: angle1: %lg angle2: %lg\n",angle1,angle2);

  circle(TopChamperInX, TopChamperInY, TopChamperInOuterR, angle1, angle2);

  glVertex3d(TopChamperOuter2X,TopChamperOuter2Y,0.0);
  glVertex3d(TopChamperOuter3X,TopChamperOuter3Y,0.0);

  glVertex3d(TopChamperOuter3X,TopChamperOuter3Y,0.0);
  glVertex3d(TopChamperOuter4X,TopChamperOuter4Y,0.0);

  glVertex3d(TopChamperOuter4X,TopChamperOuter4Y,0.0);
  glVertex3d(TopChamperOuter1X,TopChamperOuter1Y,0.0);

  glVertex3d(TopChamperOuter1X+9.0,TopChamperOuter1Y+7.0,0.0);
  glVertex3d(735.0,37.0,0.0);

  glVertex3d(TopChamperInner2X, TopChamperInner2Y, 0.0);
  glVertex3d(TopChamperInner3X, TopChamperInner3Y, 0.0);

  points2Line(799.0,88.0,TopChamperInner4X,TopChamperInner4Y,&a1,&c1);
  intersectionLineCircle(a1,c1,TopChamperOutX,TopChamperOutY,TopChamperOutR,
                         &xi1,&yi1,&xi2,&yi2);
  glVertex3d(799.0,88.0,0.0);
  glVertex3d(xi2,yi2,0.0);
  circle(TopChamperOutX,TopChamperOutY,TopChamperOutR,0.0,360.0);

  glVertex3d(Head1X,Head1Y,0.0);
  glVertex3d(Head2X,Head2Y,0.0);

  glVertex3d(Head2X,Head2Y,0.0);
  glVertex3d(Head3X,Head3Y,0.0);

  glVertex3d(Head3X,Head3Y,0.0);
  glVertex3d(Head4X,Head4Y,0.0);

  glVertex3d(Head4X,Head4Y,0.0);
  glVertex3d(Head5X,Head5Y,0.0);

  glVertex3d(Head5X,Head5Y,0.0);
  glVertex3d(Head6X,Head6Y,0.0);

  glVertex3d(Head6X,Head6Y,0.0);
  glVertex3d(Head1X,Head1Y,0.0);

  circle(CapstanX,CapstanY,CapstanR,0.0,360.0);

#ifdef USECORNER
  circle(BottomCornerX, BottomCornerY, BottomCornerOuterR, 0.0, 360.0);
  circle(BottomCornerX, BottomCornerY, BottomCornerInnerR, 0.0, 360.0);
#endif

  circle(BottomChamperInX, BottomChamperInY, BottomChamperInInnerR, 0.0, 360.0);

  points2Line(BottomChamperOuter1X, BottomChamperOuter1Y, BottomChamperOuter2X, BottomChamperOuter2Y,
              &a1, &c1);
  intersectionLineCircle(a1,c1,BottomChamperInX,BottomChamperInY,BottomChamperInInnerR,
              &xi1,&yi1,&xi2,&yi2);

  glVertex3d(xi2,yi2,0.0);
  glVertex3d(BottomChamperOuter2X,BottomChamperOuter2Y,0.0);

  intersectionLineCircle(a1,c1,BottomChamperInX,BottomChamperInY,BottomChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);

  angle1=-atan2(yi2-BottomChamperInY,xi2-BottomChamperInX)*180.0/M_PI;
  points2Line(BottomChamperOuter4X, BottomChamperOuter4Y, BottomChamperOuter1X, BottomChamperOuter1Y,
              &a1,&c1);
  intersectionLineCircle(a1,c1,BottomChamperInX,BottomChamperInY,BottomChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);
  angle2=360+atan2(yi2-BottomChamperInY,xi2-BottomChamperInX)*180.0/M_PI;

//printf("Bottom: angle1: %lg angle2: %lg\n",angle1,angle2);
  circle(BottomChamperInX, BottomChamperInY, BottomChamperInOuterR, -angle2, angle1);

  glVertex3d(BottomChamperOuter2X,BottomChamperOuter2Y,0.0);
  glVertex3d(BottomChamperOuter3X,BottomChamperOuter3Y,0.0);

  glVertex3d(BottomChamperOuter3X,BottomChamperOuter3Y,0.0);
  glVertex3d(BottomChamperOuter4X,BottomChamperOuter4Y,0.0);

  glVertex3d(BottomChamperOuter4X,BottomChamperOuter4Y,0.0);
  glVertex3d(BottomChamperOuter1X,BottomChamperOuter1Y,0.0);

  glVertex3d(BottomChamperOuter1X+9.0,BottomChamperOuter1Y-7.0,0.0);
  glVertex3d(735.0,MirrorY-37.0,0.0);

  glVertex3d(BottomChamperInner2X, BottomChamperInner2Y, 0.0);
  glVertex3d(BottomChamperInner3X, BottomChamperInner3Y, 0.0);

  points2Line(799.0,MirrorY-88.0,BottomChamperInner4X,BottomChamperInner4Y,&a1,&c1);
  intersectionLineCircle(a1,c1,BottomChamperOutX,BottomChamperOutY,BottomChamperOutR,
                         &xi1,&yi1,&xi2,&yi2);
  glVertex3d(799.0,MirrorY-88.0,0.0);
  glVertex3d(xi2,yi2,0.0);
  circle(BottomChamperOutX,BottomChamperOutY,BottomChamperOutR,0.0,360.0);

  // Tape:

  glEnd();
  glColor4d(150.0/255.0,75.0/255.0,0.0,1.0);
  glBegin(GL_TRIANGLES);
  w->magTape.inRadius[unitNo]=length2Radius(w,w->magTape.INlength[unitNo])*PIXELSPERM;
  w->magTape.inRotation[unitNo]=-fmod(length2Rotation(w,w->magTape.INlength[unitNo])*360.0,360.0);
  w->magTape.outRadius[unitNo]=length2Radius(w,w->magTape.OUTlength[unitNo])*PIXELSPERM;
//printf("inRadius: %g outRadius: %g\n",inRadius,outRadius);
  w->magTape.outRotation[unitNo]=fmod(length2Rotation(w,w->magTape.OUTlength[unitNo])*360.0,360.0);
  disk(TopReelX,TopReelY,0.0,TopReelInnerR,w->magTape.inRadius[unitNo]);
  disk(BottomReelX,BottomReelY, 0.5,BottomReelInnerR,w->magTape.outRadius[unitNo]);
  glEnd();
  glBegin(GL_LINES);

#ifdef USECORNER
  twoCirclesTangent(TopReelX,TopReelY,w->magTape.inRadius[unitNo],
                    TopCornerX,TopCornerY,TopCornerInnerR,0,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi11,yi11,0.0);
  glVertex3d(xi21,yi21,0.0);

  twoCirclesTangent(TopCornerX,TopCornerY,TopCornerInnerR,
                    TopChamperInX,TopChamperInY,TopChamperInInnerR,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);
#else
  twoCirclesTangent(TopReelX,TopReelY,w->magTape.inRadius[unitNo],
                    TopChamperInX,TopChamperInY,TopChamperInInnerR,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);
#endif

  xcenter1=(TopChamperInner1X+TopChamperInner4X)/2.0;
  ycenter1=(TopChamperInner1Y+TopChamperInner4Y)/2.0;
  xcenter2=(TopChamperInner2X+TopChamperInner3X)/2.0;
  ycenter2=(TopChamperInner2Y+TopChamperInner3Y)/2.0;
  points2Line(xcenter1,ycenter1,xcenter2,ycenter2,&acenter,&ccenter);
  x=xcenter1+(xcenter2-xcenter1)*(w->magTape.inRatio[unitNo]+0.5);
  y=-ccenter-acenter*x;
  r1=fabs(acenter*TopChamperInner1X+TopChamperInner1Y+ccenter)/sqrt(SQR(acenter)+1.0)-tapewidth*0.5;
  angle3=atan(acenter)*180.0/M_PI;
//printf("angle3: %g\n",angle3);
//glVertex3d(xcenter1,-ccenter-acenter*xcenter1,0.0);
//glVertex3d(xcenter2,-ccenter-acenter*xcenter2,0.0);
  circle(x,y,r1,angle3-90.0,angle3+90.0);

  x2=r1*cos((angle3+90.0)/180.0*M_PI)+x;
  y2=r1*sin((angle3-90.0)/180.0*M_PI)+y;

  x3=xcenter1+5.0;
  y3=-ccenter-acenter*x3;
  x1=r1*cos((angle3+90.0)/180.0*M_PI)+x3;
  y1=r1*sin((angle3-90.0)/180.0*M_PI)+y3;
  glVertex3d(x1,y1,0.0);
  glVertex3d(x2,y2,0.0);

  x2=r1*cos((angle3-90.0)/180.0*M_PI)+x;
  y2=r1*sin((angle3-270.0)/180.0*M_PI)+y;
  
  x3=xcenter1+15.0;
  y3=-ccenter-acenter*x3;
  x1=r1*cos((angle3-90.0)/180.0*M_PI)+x3;
  y1=r1*sin((angle3-270.0)/180.0*M_PI)+y3;
  glVertex3d(x1,y1,0.0);
  glVertex3d(x2,y2,0.0);

  twoCirclesTangent(TopChamperOutX,TopChamperOutY,TopChamperOutR,
                    Head3X,Head3Y,tapewidth*0.5,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);
  x=xi21+Head4X-Head3X;
  y=yi21;
  glVertex3d(xi21,yi21,0.0);
  glVertex3d(x,y,0.0);
  
  twoCirclesTangent(Head4X,Head4Y,tapewidth*0.5,
                    CapstanX,CapstanY,CapstanR,0,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(x,y,0.0);
  glVertex3d(xi22,yi22,0.0);

  twoCirclesTangent(CapstanX,CapstanY,CapstanR,
                    BottomChamperOutX,BottomChamperOutY,BottomChamperOutR,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);

  xcenter1=(BottomChamperInner1X+BottomChamperInner4X)/2.0;
  ycenter1=(BottomChamperInner1Y+BottomChamperInner4Y)/2.0;
  xcenter2=(BottomChamperInner2X+BottomChamperInner3X)/2.0;
  ycenter2=(BottomChamperInner2Y+BottomChamperInner3Y)/2.0;
  points2Line(xcenter1,ycenter1,xcenter2,ycenter2,&acenter,&ccenter);
  x=xcenter1+(xcenter2-xcenter1)*(w->magTape.outRatio[unitNo]+0.5);
  y=-ccenter-acenter*x;
  r1=fabs(acenter*BottomChamperInner1X+BottomChamperInner1Y+ccenter)/
     sqrt(SQR(acenter)+1.0)-tapewidth*0.5;
  angle3=atan(acenter)*180.0/M_PI;
  circle(x,y,r1,angle3-90.0,angle3+90.0);

  x2=r1*cos((angle3+90.0)/180.0*M_PI)+x;
  y2=r1*sin((angle3-90.0)/180.0*M_PI)+y;
  x3=xcenter1+15;
  y3=-ccenter-acenter*x3;
  x1=r1*cos((angle3+90.0)/180.0*M_PI)+x3;
  y1=r1*sin((angle3-90.0)/180.0*M_PI)+y3;
  glVertex3d(x1,y1,0.0);
  glVertex3d(x2,y2,0.0);

  x2=r1*cos((angle3-90.0)/180.0*M_PI)+x;
  y2=r1*sin((angle3-270.0)/180.0*M_PI)+y;
  x3=xcenter1+5.0;
  y3=-ccenter-acenter*x3;
  x1=r1*cos((angle3-90.0)/180.0*M_PI)+x3;
  y1=r1*sin((angle3-270.0)/180.0*M_PI)+y3;
  glVertex3d(x1,y1,0.0);
  glVertex3d(x2,y2,0.0);

#ifdef USECORNER
  twoCirclesTangent(BottomReelX,BottomReelY,outRadius,
                    BottomCornerX,BottomCornerY,BottomCornerInnerR,0,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi11,yi11,0.0);
  glVertex3d(xi21,yi21,0.0);

  twoCirclesTangent(BottomCornerX,BottomCornerY,BottomCornerInnerR,
                    BottomChamperInX,BottomChamperInY,BottomChamperInInnerR,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);
#else
  twoCirclesTangent(BottomReelX,BottomReelY,w->magTape.outRadius[unitNo],
                    BottomChamperInX,BottomChamperInY,BottomChamperInInnerR,1,
		    &xi11,&yi11,&xi12,&yi12,&xi21,&yi21,&xi22,&yi22);
  glVertex3d(xi12,yi12,0.0);
  glVertex3d(xi21,yi21,0.0);
#endif


  glEnd();

// Label on top reel:
//#define TopReelLabelWidth 100.0
//#define TopReelLabelHeight 50.0
//#define TopReelLabelDistance 100.0

// kilroy
  glPushMatrix();
  glTranslated(TopReelX,TopReelY,0.0);
  glRotated(w->magTape.inRotation[unitNo],0.0,0.0,1.0);
  glPolygonMode(GL_FRONT, GL_FILL);
  glPolygonMode(GL_BACK, GL_FILL);
  glBegin(GL_QUADS);
    glColor4d(0.8,0.8,0.8,0.0);
    glVertex3d(-TopReelLabelWidth/2.0, TopReelLabelDistance+TopReelLabelHeight/2.0, 0.5);
    glVertex3d( TopReelLabelWidth/2.0, TopReelLabelDistance+TopReelLabelHeight/2.0, 0.5);
    glVertex3d( TopReelLabelWidth/2.0, TopReelLabelDistance-TopReelLabelHeight/2.0, 0.5);
    glVertex3d(-TopReelLabelWidth/2.0, TopReelLabelDistance-TopReelLabelHeight/2.0, 0.5);
  glEnd();
  glColor4d(0.0,0.0,0.0,0.0);
//glTranslated(-TopReelLabelWidth/2.0, TopReelLabelDistance+TopReelLabelHeight/2.0, 1.0);
  glTranslated(0.0, TopReelLabelDistance, 1.0);
  bbox(1.0, &textXmin, &textXmax, &textYmin, &textYmax, w->magTape.tapeLabels[unitNo]);
#ifdef DEBUG
  printf("%d: textXmin: %g textXmax: %g textYmin: %g textYmax: %g\n",
      unitNo, textXmin, textXmax, textYmin, textYmax);
#endif
  sx=TopReelLabelWidth/(textXmax-textXmin);
  sy=TopReelLabelHeight/(textYmax-textYmin);
  s=sx;
  if(s>sy) s=sy;
#ifdef DEBUG
  printf("%d: sx: %g sy: %g s: %g\n",unitNo, sx,sy,s);
#endif
  glScaled(s,-s,1.0);
  glTranslated(-(textXmin+textXmax)*0.5, -(textYmin+textYmax)*0.5, 0.0);
  glBegin(GL_LINES);
  stroke(1.0, 0.0, 0.0, 0.0, w->magTape.tapeLabels[unitNo]);
  glEnd();

  glPopMatrix();
  
  glEnable(GL_BLEND);
  glPushMatrix();
  glTranslated(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0);
  glScaled(BottomReelOuterR*2.0,BottomReelOuterR*2.0,1.0);
  glTranslated(0.5,0.5,0.0);
  glRotated(w->magTape.outRotation[unitNo],0.0,0.0,1.0);
  glTranslated(-0.5,-0.5,0.0);
  
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // scale linearly when image bigger than texture
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // scale linearly when image smalled than texture

    // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, 
    // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself.
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w->magTape.image1->sizeX, w->magTape.image1->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, w->magTape.image1->data);
  glEnable(GL_TEXTURE_2D);			// Enable Texture Mapping
  glBindTexture(GL_TEXTURE_2D, w->magTape.texture[0]);   // choose the texture to use.


  glBegin(GL_QUADS);
    glColor4d(1.0,1.0,1.0,1.0);
    glTexCoord2d(0.0, 0.0);
    glVertex3d(0.0, 0.0, 1.0);
    glTexCoord2d(0.0, 1.0);
    glVertex3d(0.0, 1.0, 1.0);
    glTexCoord2d(1.0, 1.0);
    glVertex3d(1.0, 1.0, 1.0);
    glTexCoord2d(1.0, 0.0);
    glVertex3d(1.0, 0.0, 1.0);

  glEnd();
  glPopMatrix();

}

static void Realize(w, valueMask, attributes)
XsMagTapeWidget w;
Mask *valueMask;
XSetWindowAttributes *attributes;
{
  int i;

#ifdef DEBUG
  printf("Realize called.\n");
#endif
   
    XtCreateWindow ((Widget) w, (unsigned int)InputOutput,
		    CopyFromParent,
		    *valueMask, attributes);

#ifdef DEBUG
  printf("Realize: glXCreateContext.\n");
#endif

  w->magTape.cmap = DefaultColormap(XtDisplay(w), DefaultScreen(XtDisplay(w)));

  w->magTape.glx_context = glXCreateContext(XtDisplay(w), w->magTape.visualInfo, 0, GL_TRUE);
  if(w->magTape.glx_context == NULL)
  {
    printf("glx context failed.\n");
    exit(0);
  }
#ifdef DEBUG
  printf("glXMakeCurrent 2\n" );
#endif
  glXMakeCurrent(XtDisplay(w), XtWindow(w), w->magTape.glx_context);
  LoadGLTextures(w);

#ifdef DEBUG
  printf("Realize done.\n");
#endif
  Redisplay(w, NULL, NULL);
}

static int magTape_attrib0[] ={GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8,
                            GLX_ALPHA_SIZE, 8, GLX_DEPTH_SIZE, 16, None};

static void createVisualInfo(XsMagTapeWidget w)
{
    static XVisualInfo *visualInfo;

    w->magTape.visualInfo = glXChooseVisual(XtDisplay(w),
					XScreenNumberOfScreen(XtScreen(w)),
					magTape_attrib0);
    if (!w->magTape.visualInfo)
    {
      fprintf(stderr, "Error: Couldn't find any suitable visual for GL.\n");
      exit(-1);
    }
}

static void Initialize(request, new, args, num_args)
XsMagTapeWidget request, new;
ArgList args;
Cardinal *num_args;
{
#ifdef DEBUG
  printf("Initialize called. num_args = %d\n", *num_args);
#endif
  if(request->core.width == 0) new->core.width = wXMAX*3;
  if(request->core.height == 0) new->core.height = wYMAX*2;

  /* determine the visual info if needed */
  if (new->magTape.visualInfo == NULL)
  {
      new->magTape.myVisual = TRUE;
#ifdef DEBUG
      printf("Initialize: createVisualInfo\n");
#endif
      createVisualInfo(new);
  }
  else
  {
#ifdef DEBUG
      printf("Initialize: myVisual = FALSE\n");
#endif
      new->magTape.myVisual = FALSE;
  }

  new->core.depth = new->magTape.visualInfo->depth;
#ifdef DEBUG
  printf("Initialize: XtGetApplicationResources\n");
#endif

  XsmagTapeInsertDefaultValues(new);
  XsmagTapeInsertValues(new);
#ifdef DEBUG
  printf("Initialize done\n");
#endif
}

static void Destroy(w)
XsMagTapeWidget w;
{
  /*
	This must release all GC's and
	remove all callbacks
  */
#ifdef DEBUG
  printf("MagTape: Destroy called.\n");
#endif

  XtRemoveAllCallbacks((Widget) w, XtNdone);

  if (w->magTape.myVisual && w->magTape.visualInfo)
      XtFree ((XtPointer)w->magTape.visualInfo);

}

static void Resize(w)
XsMagTapeWidget w;
{
  long glxsize, glysize;
  /*
	Recalculates everything.
  */
#ifdef DEBUG
  printf("MagTape: Resize called.\n");
#endif

  if(!XtIsRealized((Widget) w))
  {
#ifdef DEBUG
    printf("MagTape: Isn't realized.\n");
#endif
    return;
  }

#ifdef DEBUG
  printf("glXMakeCurrent 3\n");
#endif
  glXMakeCurrent(XtDisplay(w), XtWindow(w), w->magTape.glx_context);

}

static void Redisplay(w, event, region)
XsMagTapeWidget w;
XEvent *event;
Region region;
{
  int unitNo;
#ifdef DEBUG
  printf("MagTape: Redisplay entry.\n");
#endif

#ifdef DEBUG
  printf("glXMakeCurrent 4\n");
#endif
  glXMakeCurrent(XtDisplay(w), XtWindow(w), w->magTape.glx_context);

  glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);

  for(unitNo=0; unitNo<w->magTape.ndrives; unitNo++)
  {
    DrawGLScene(w,unitNo);
  }

  // since this is double buffered, swap the buffers to display what just got drawn.
#ifdef DEBUG
  printf("swap buffers\n");
#endif
  glXSwapBuffers(XtDisplay(w), XtWindow(w));
#ifdef DEBUG
  printf("Redisplay exit.\n");
#endif
}

static Boolean SetValues(current, request, new)
XsMagTapeWidget current, request, new;
{
#ifdef DEBUG
  printf("SetValues called\n");
#endif
  XsmagTapeInsertValues(new);

#ifdef DEBUG
  printf("SetValues exit\n");
#endif
  return(TRUE); /* Always redraw */
}

static void enter_magTape(w, event)
XsMagTapeWidget w;
XEvent *event;
{
}

static void leave_magTape(w, event)
XsMagTapeWidget w;
XEvent *event;
{
}

static void set_update_mode(w)
XsMagTapeWidget w;
{
  int i,j;

}

static void XsmagTapeInsertValues(w)
XsMagTapeWidget w;
{
  int i;
#ifdef DEBUG
  printf("XsmagTapeInsertValues entry.\n");
#endif

  if(w->magTape.ptapelabels != NULL)
  {
    for(i=0; i<NDRIVES; i++)
    {
      if(w->magTape.tapeLabels[i] != NULL)
      {
	free(w->magTape.tapeLabels[i]);
      }
#ifdef DEBUG
      printf("ptapelabels[%d]: %s\n",i,w->magTape.ptapelabels[i]);
#endif
      w->magTape.tapeLabels[i] = malloc(strlen(w->magTape.ptapelabels[i])+1);
      strcpy(w->magTape.tapeLabels[i], w->magTape.ptapelabels[i]);
    }
  }
}

static void XsmagTapeInsertDefaultValues(w)
XsMagTapeWidget w;
{
  int i;

#ifdef DEBUG
  printf("XsmagTapeInsertDefaultValues entry.\n");
#endif

  for(i=0;i<NDRIVES;i++)
  {
    w->magTape.inTape[i] = MAXCHAMPERTAPELENGTH*0.5;
    w->magTape.outTape[i] = MAXCHAMPERTAPELENGTH*0.5;
    w->magTape.inError[i] = 0.0;
    w->magTape.outError[i] = 0.0;
    w->magTape.OUTlength[i] = 0.0;
    w->magTape.INlength[i] = TAPELENGTH;
    w->magTape.tapeappetite[i] = 0.0;
    w->magTape.status[i] = TAPE_IDLE;
    w->magTape.readstart[i] = 0;
    w->magTape.taperead[i] = 0;
    w->magTape.UAKK[i] = 4;
    w->magTape.capstanAcceleration[i] = 0;
  }
  clock_gettime(CLOCK_MONOTONIC, &(w->magTape.time0));
  w->magTape.thickness = THICKNESS;
  w->magTape.radius0 = 0.113/2.0;
  w->magTape.lastsecs=seconds(w);
  w->magTape.timerRunning=0;

#ifdef DEBUG
  printf("XsmagTapeInsertDefaultValues exit.\n");
#endif
}

static XComposeStatus compose={NULL,0};
static int lastUnitNo=0;

static void InsertChar(XsMagTapeWidget w, XEvent *event)
{
  unsigned char strbuf[100];
  KeySym keycode;
  int ilen,unitNo;
  ilen=XLookupString((XKeyEvent *)event, strbuf, 100, &keycode, &compose);
  strbuf[ilen]='\0';
  printf("strbuf: %s %ld\n",strbuf,keycode);
  if(keycode==65360)
  {
    /* home */
    for(unitNo=0; unitNo<w->magTape.ndrives; unitNo++)
    {
      XsmagTapeCommand(w, unitNo, TAPE_REWIND, 0.0);
    }
  }
  else if(keycode>=48 && keycode<=53)
  {
    lastUnitNo = keycode-48;
  }
  else if(keycode == 'f')
  {
    XsmagTapeCommand(w, lastUnitNo, TAPE_FORWARD, BLOCKLEN(200,0,0));
  }
}


static void done_magTape(XsMagTapeWidget w,int unitNo)
{
  static long lunitNo;

  lunitNo=unitNo;

  if(XtHasCallbacks((Widget) w, XtNdone) == XtCallbackHasSome)
  {
#ifdef DEBUG
    printf("%d: done_magTape\n",unitNo);
#endif
    XtCallCallbacks((Widget) w, XtNdone, &lunitNo);
  }
}

/* Exported functions */

int XsmagTapeIdle(XsMagTapeWidget w,int unitNo)
{
  return(w->magTape.status[unitNo] == TAPE_IDLE);
}

int XsmagTapeCommand(XsMagTapeWidget w, int unitNo, int cmd, double parameter)
{
  int ret=1;
#ifdef DEBUG
  printf("%d: XsmagTapeCommand status: %d taperead: %g\n", unitNo, w->magTape.status[unitNo], w->magTape.taperead[unitNo]);
#endif
  if(w->magTape.status[unitNo]!=TAPE_IDLE)
  {
#ifdef DEBUG
    printf("%d TapeCommand: Tape is already running: %d!\n", unitNo, w->magTape.status[unitNo]);
#endif
    return 1;
  }
  if(cmd==TAPE_SETPOS)
  {
    if(parameter==0.0)
    {
      cmd=TAPE_REWIND;
    }
    else if(parameter>w->magTape.taperead[unitNo])
    {
      cmd=TAPE_FORWARD;
      parameter -= w->magTape.taperead[unitNo];
    }
    else if(parameter<w->magTape.taperead[unitNo])
    {
      cmd=TAPE_REVERSE;
      parameter = w->magTape.taperead[unitNo]-parameter;
    }
    else
    {
      /* already at position */
      return 0;
    }
  }
  switch(cmd)
  {
    case TAPE_REWIND:
      if(w->magTape.taperead[unitNo]>0)
      {
	w->magTape.capstanAcceleration[unitNo]=-CAPSTANACCELERATION;
	w->magTape.UAKK[unitNo]=22;
	w->magTape.status[unitNo]=REWIND_ACCELERATE;
#ifdef DEBUG
	printf("%d: start REWIND\n", unitNo);
#endif
	ret=0;
      }
      break;
    case TAPE_FORWARD:
      w->magTape.readstart[unitNo]=w->magTape.taperead[unitNo];
      w->magTape.tapeappetite[unitNo]=parameter;
      w->magTape.capstanAcceleration[unitNo]=CAPSTANACCELERATION;
      w->magTape.UAKK[unitNo]=4;
      w->magTape.status[unitNo]=FORWARD_ACCELERATE;
#ifdef DEBUG
      printf("%d: start FORWARD\n", unitNo);
#endif
      ret=0;
      break;
    case TAPE_REVERSE:
      if(w->magTape.taperead[unitNo]>0)
      {
	w->magTape.readstart[unitNo]=w->magTape.taperead[unitNo];
	w->magTape.tapeappetite[unitNo]=parameter;
	w->magTape.capstanAcceleration[unitNo]=-CAPSTANACCELERATION;
	w->magTape.UAKK[unitNo]=4;
	w->magTape.status[unitNo]=REVERSE_ACCELERATE;
#ifdef DEBUG
	printf("%d: start REVERSE\n", unitNo);
#endif
	ret= 0;
      }
      break;
  }
#ifdef DEBUG
  printf("%d: XsmagTapeCommand: timerRunning I: %d\n",unitNo,w->magTape.timerRunning);
#endif
  if(!w->magTape.timerRunning)
  {
    setTimerRunning(w);
#ifdef DEBUG
    printf("%d: XsmagTapeCommand: timerRunning II: %d\n",unitNo,w->magTape.timerRunning);
#endif
    if(w->magTape.timerRunning)
    {
      w->magTape.lastsecs=seconds(w);
      startTimer(w);
    }
  }
  fflush(stdout);
  return(ret) ;
}

