
static char CompassFace_id[] = "$Header: CompassFace.cc,v 1.3 97/10/03 09:28:22 oreilly Exp $";

/*
$Log:	CompassFace.cc,v $
Revision 1.3  97/10/03  09:28:22  09:28:22  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.2  97/03/20  12:28:51  12:28:51  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.1  96/10/28  09:11:52  09:11:52  oreilly (Thomas C. O'Reilly)
Initial revision

*/
#include <Vk/VkApp.h>
#include <values.h>
#include <sys/param.h>
#include "CompassFace.h"

#define dprintf if (debug) fprintf

XtResource CompassFace::_resources[] = 
{
  {
    "ticColor",
    "TicColor",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(CompassFace *, _ticColor),
    XmRString,
    "white"
  },

  {
    "faceColor",
    "FaceColor",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(CompassFace *, _faceColor),
    XmRString,
    "black"
  },

  {
    "historyColor",
    "HistoryColor",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(CompassFace *, _historyColor),
    XmRString,
    "green"
  },

  {
    "autoHeadingColor",
    "AutoHeadingColor",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(CompassFace *, _autoHeadingColor),
    XmRString,
    "orange"
  },

  {
    "setpointColor",
    "SetpointColor",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(CompassFace *, _setpointColor),
    XmRString,
    "yellow"
  }

};

CompassFace::CompassFace(const char *name, Widget parent, int nHistoryPoints)
  : VkDoubleBuffer(name, parent)
{
  _history = 0;
  
  getResources(_resources, XtNumber(_resources));

  nHistoryPoints = MAX(nHistoryPoints, 0);
  
  _history = new CircularQueue<HistoryPoint>(nHistoryPoints);
  if (_history->error())
  {
    return;
  }
  
  _display = theApplication->display();
  _screenNum = DefaultScreen(_display);
  
  XGCValues gcValues;
  gcValues.foreground = _ticColor;
  gcValues.background = _faceColor;
  _faceGC = XCreateGC(_display, RootWindow(_display, _screenNum), 
		      (GCForeground | GCBackground), &gcValues);

  gcValues.foreground = _historyColor;
  _headingGC = XCreateGC(_display, RootWindow(_display, _screenNum), 
			 (GCForeground | GCBackground), &gcValues);
  
  XSetLineAttributes(_display, _headingGC, 2, LineSolid, CapButt, JoinBevel);

  // Get colors for 3D effect
  Pixel foreground, background, selectColor;
  Colormap colormap;
  
  XtVaGetValues(_baseWidget, XmNbackground, &background, 
		XmNcolormap, &colormap, NULL);
  
  XmGetColors(XtScreen(_baseWidget), colormap, background, 
	      &foreground, &_topShadowColor, &_bottomShadowColor, 
	      &selectColor);

  _fontInfo = XQueryFont(_display, XGContextFromGC(_faceGC));
  
  _bigTicIncr = 45;
  _smallTicIncr = 15;
  _headingTicLength = 10;
  
  setHeading(0.);
  setSetpoint(0.);
  clearAutoHeading();
  
  _xRange = _yRange = 2.1;
  _dialRadius = 1.0;
  _bigTicRadius = 0.85;
  _smallTicRadius = 0.90;
  _labelRadius = 0.7;
  _autoLabelRadius = 0.6;
  
  _historyRadiusIncr = _dialRadius / nHistoryPoints;
  
  equalizeScales();
  clearAutoHeading();
}


CompassFace::~CompassFace()
{
  delete _history;
  XFreeFont(_display, _fontInfo);
}


void CompassFace::setHeading(Radians heading)
{
  _heading = heading;
  Math::zeroToTwoPi(&_heading);
}


void CompassFace::setAutoHeading(Radians heading)
{
  _autoHeading = heading;
  Math::zeroToTwoPi(&_autoHeading);
}


void CompassFace::clearAutoHeading()
{
  _autoHeading = NoAutoHeading;
}


void CompassFace::setSetpoint(Radians setpoint)
{
  _setpoint = setpoint;
  Math::zeroToTwoPi(&_setpoint);
}


void CompassFace::addHistory(Radians heading)
{
  HistoryPoint point;
  point.sinHeading = sin(heading);
  point.cosHeading = cos(heading);
  
  _history->add(&point);
}


void CompassFace::clearHistory()
{
  _history->clear();
}


void CompassFace::resize()
{
  Boolean debug = False;
  
  dprintf(stderr, "CompassFace::resize()\n");
  dprintf(stderr, "Old: _width=%d  _height=%d\n", _width, _height);
  VkDoubleBuffer::resize();
  dprintf(stderr, "New: _width=%d  _height=%d\n", _width, _height);    
  draw();
}


void CompassFace::draw()
{
  equalizeScales();
  drawDial();

  if (_history->maxQueueSize())
    drawHistory();
}


void CompassFace::drawDial()
{
  int i0, j0;
  int i1, j1, i2, j2;

  // Draw current heading tic
  physicalCoords(0., _dialRadius, &i0, &j0);
  XSetLineAttributes(_display, _faceGC, 2, LineSolid, CapButt, JoinBevel);
  XDrawLine(_display, _canvas, _faceGC, i0, j0, i0, 0);
  XSetLineAttributes(_display, _faceGC, 2, LineSolid, CapButt, JoinBevel);
  
  // Draw face 
  physicalCoords(-_dialRadius, _dialRadius, &i0, &j0);
  physicalCoords(_dialRadius, -_dialRadius, &i1, &j1);
  
  int xDiameter = i1 - i0;
  int yDiameter = j1 - j0;

  XSetForeground(_display, _faceGC, _faceColor);  

  XFillArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	   0, FullCircle);

  XSetLineAttributes(_display, _faceGC, 1, LineSolid, CapButt, JoinBevel);
  XSetForeground(_display, _faceGC, _bottomShadowColor);  
  XDrawArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	   0, 180 * 64);

  XSetLineAttributes(_display, _faceGC, 2, LineSolid, CapButt, JoinBevel);
  XDrawArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	   -315 * 64, -90 * 64);
  
  XSetLineAttributes(_display, _faceGC, 1, LineSolid, CapButt, JoinBevel);
  XSetForeground(_display, _faceGC, _topShadowColor);  
  XDrawArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	   0, -180 * 64);

  XSetLineAttributes(_display, _faceGC, 2, LineSolid, CapButt, JoinBevel);
  XDrawArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	   -45 * 64, -90 * 64);

  XSetLineAttributes(_display, _faceGC, 1, LineSolid, CapButt, JoinBevel);
  XSetForeground(_display, _faceGC, _ticColor);    

  char headingStr[10];
  double alpha;
  double sinAlpha, cosAlpha; 
  double x1, y1, x2, y2;
  double radius = _dialRadius * 0.1;
  
  // Draw cross in center of dial
  alpha = dialAngle(0.);
  cosAlpha = cos(alpha);
  sinAlpha = sin(alpha);
  physicalCoords(radius * cosAlpha, radius * sinAlpha, &i1, &j1);
  physicalCoords(-radius * cosAlpha, -radius * sinAlpha, &i2, &j2);
  XDrawLine(_display, _canvas, _faceGC, i1, j1, i2, j2);

  alpha = dialAngle(M_PI_2);
  cosAlpha = cos(alpha);
  sinAlpha = sin(alpha);
  physicalCoords(radius * cosAlpha, radius * sinAlpha, &i1, &j1);
  physicalCoords(-radius * cosAlpha, -radius * sinAlpha, &i2, &j2);
  XDrawLine(_display, _canvas, _faceGC, i1, j1, i2, j2);

  for (int degrees = 0; degrees < 360; degrees += _smallTicIncr)
  {
    double ticHeading = degrees * Math::RadsPerDeg;
    
    alpha = dialAngle(ticHeading);
    cosAlpha = cos(alpha);
    sinAlpha = sin(alpha);

    if (bigTic(degrees))
      radius = _bigTicRadius;
    else
      radius = _smallTicRadius;
    
    x1 = radius * cosAlpha;
    y1 = radius * sinAlpha;
    physicalCoords(x1, y1, &i1, &j1);
    x2 = _dialRadius * cosAlpha;
    y2 = _dialRadius * sinAlpha;
    physicalCoords(x2, y2, &i2, &j2);

    XDrawLine(_display, _canvas, _faceGC, i1, j1, i2, j2);

    if (!bigTic(degrees))
      continue;
    
    x1 = _labelRadius * cosAlpha;
    y1 = _labelRadius * sinAlpha;
    physicalCoords(x1, y1, &i1, &j1);

    // Label tic
    if (degrees == 0.)
      sprintf(headingStr, "N");
    else if (degrees == 90.)
      sprintf(headingStr, "E");
    else if (degrees == 180.)
      sprintf(headingStr, "S");
    else if (degrees == 270.)
      sprintf(headingStr, "W");
    else
      sprintf(headingStr, "%d", (int )(ticHeading / Math::RadsPerDeg));

    int width = XTextWidth(_fontInfo, headingStr, strlen(headingStr));
    int height = _fontInfo->ascent + _fontInfo->descent;

    i2 = i1 - width / 2;
    j2 = j1 + height / 2;
    
    XDrawString(_display, _canvas, _faceGC, i2, j2, headingStr, 
		strlen(headingStr));
  }

  // Draw setpoint tic
  alpha = dialAngle(_setpoint);
  cosAlpha = cos(alpha);
  sinAlpha = sin(alpha);
  x1 = _labelRadius * cosAlpha;
  y1 = _labelRadius * sinAlpha;
  physicalCoords(x1, y1, &i1, &j1);
  x2 = _dialRadius * cosAlpha;
  y2 = _dialRadius * sinAlpha;
  physicalCoords(x2, y2, &i2, &j2);

  XSetLineAttributes(_display, _faceGC, 2, LineSolid, CapButt, JoinBevel);
  XSetForeground(_display, _faceGC, _setpointColor);
  XDrawLine(_display, _canvas, _faceGC, i1, j1, i2, j2);

  // Draw autoheading tic
  if (_autoHeading != NoAutoHeading)
  {
    alpha = dialAngle(_autoHeading);
    cosAlpha = cos(alpha);
    sinAlpha = sin(alpha);
    x1 = _labelRadius * cosAlpha;
    y1 = _labelRadius * sinAlpha;
    physicalCoords(x1, y1, &i1, &j1);
    x2 = _dialRadius * cosAlpha;
    y2 = _dialRadius * sinAlpha;
    physicalCoords(x2, y2, &i2, &j2);

    XSetForeground(_display, _faceGC, _autoHeadingColor);
    XDrawLine(_display, _canvas, _faceGC, i1, j1, i2, j2);

    int arcLen = 
      (int )(Math::angularSeparation(_heading, _autoHeading) / 
	     Math::RadsPerDeg + 0.5);
    
    if (arcLen > 180)
      arcLen = -(360 - arcLen);
    
    XDrawArc(_display, _canvas, _faceGC, i0, j0, xDiameter, yDiameter,
	     90 * 64, -arcLen * 64);
    

    XSetLineAttributes(_display, _faceGC, 1, LineSolid, CapButt, JoinBevel);
    XSetForeground(_display, _faceGC, _ticColor);

#ifdef ZILCH
    // Label tic
    x1 = _autoLabelRadius * cosAlpha;
    y1 = _autoLabelRadius * sinAlpha;
    physicalCoords(x1, y1, &i1, &j1);
    sprintf(headingStr, "A");
    int width = XTextWidth(_fontInfo, headingStr, strlen(headingStr));
    int height = _fontInfo->ascent + _fontInfo->descent;

    i2 = i1 - width / 2;
    j2 = j1 + height / 2;
    
    XDrawString(_display, _canvas, _faceGC, i2, j2, headingStr, 
		strlen(headingStr));
#endif

  }
}


void CompassFace::drawHistory()
{
  double x, y;
  int i, j;
  int prevI, prevJ;
  physicalCoords(0., 0., &prevI, &prevJ);

  _history->startIter();

  HistoryPoint *point;

  double radius = _dialRadius - _historyRadiusIncr * _history->length();
  double sinA = sin(M_PI_2 + _heading);
  double cosA = cos(M_PI_2 + _heading);
  
  for (int k = 0; k < _history->maxQueueSize(); k++)
  {
    if (point = _history->next())
    {
      x = radius * (cosA * point->cosHeading + sinA * point->sinHeading);
      y = radius * (sinA * point->cosHeading - cosA * point->sinHeading);
      
      physicalCoords(x, y, &i, &j);
      if ((k > 0) || _history->length() == _history->maxQueueSize())
	XDrawLine(_display, _canvas, _headingGC, i, j, prevI, prevJ);
    }
    prevI = i;
    prevJ = j;
    radius += _historyRadiusIncr;
  }
}


/* Convert x and y to physical coordinates. Origin is always at center of
graph area, and x=0, y=0. X ranges from -1 to 1, as does y. */
int CompassFace::physicalCoords(double x, double y,
				int *i, int *j)
{
  double value;
  double xmin = -_xRange / 2.;
  value = (x - xmin) * _width / _xRange;

  if (_width > _height)
    value /= _equalFactor;
  
  if (fabs(value) > MAXINT)
    return -1;
  
  *i = (int )(value + 0.5);
  
  double ymin = -_yRange / 2.;
  value = _height - ((y - ymin) * _height / _yRange);

  if (_height > _width)
    value *= _equalFactor;
  
  if (fabs(value) > MAXINT)
    return -1;
  
  *j = (int )(value + 0.5);
  
  return 0;

}


Radians CompassFace::dialAngle(Radians heading)
{
#ifdef ZILCH 
  double delta = heading - _heading;
  Math::zeroToTwoPi(&delta);
  return M_PI_2 - delta;
#endif
  return M_PI_2 - heading + _heading;
}
