/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


/*---------------------------------------------------------------------------*\
 $Source: /home/gda/dxcvs/dx/src/exec/hwrender/hwRotateInteractor.c,v $
  Author: Mark Hood

  This file contains the implementation of the TDM rotation interactor.  

\*---------------------------------------------------------------------------*/

#include <stdio.h>
#include <math.h>
#include "hwDeclarations.h"
#include "hwRotateInteractor.h"
#include "hwInteractorEcho.h"
#include "hwMatrix.h"
 
#ifndef STANDALONE
#include "hwMemory.h"
#endif

#include "hwPortLayer.h"

#include "hwDebug.h"

/*
 *  Forward references
 */

static void DoubleClick (tdmInteractor, int, int, tdmInteractorReturn *) ;
static void StartStroke (tdmInteractor, int, int, int, int) ;
static void StartViewStroke (tdmInteractor, int, int, int, int) ;
static void StartRotateStroke (tdmInteractor, int, int, int, int) ;
static void EndStroke (tdmInteractor, tdmInteractorReturnP) ;
static void ResumeEcho (tdmInteractor, tdmInteractorRedrawMode) ;
static void Destroy (tdmInteractor) ;

static void InitTwirlStroke (tdmInteractor, float, float) ;
static void Twirl (tdmInteractor, int, int, int, int) ;
static void InitRollStroke (tdmInteractor, int, int) ;
static void ViewRoll (tdmInteractor, int, int, int, int) ;
static void PlaneRollWithEcho (tdmInteractor, int, int, int, int) ;
static int PlaneRoll (tdmInteractor I, int x, int y, float rot[4][4]) ;

static void globeConfig (tdmInteractor,
			 int *, int *, tdmInteractorRedrawMode) ;
static void gnomonConfig (tdmInteractor,
			  int *, int *, tdmInteractorRedrawMode) ;
static void viewerConfig (tdmInteractor,
			  int *, int *, tdmInteractorRedrawMode) ;
static void RestoreConfig (tdmInteractor,
			   int, int, tdmInteractorRedrawMode) ;

static void extractViewRot (float M[4][4]) ;
static void orbitFrom (float R[4][4], float current[4][4],
		       float to[3], float vdist) ;

/*
 *  Null functions
 */

static void NullFunction()
{
}

static void
NullStartStroke (tdmInteractor I, int x, int y, int btn, int s)
{
}

static void
NullDoubleClick (tdmInteractor I, int x, int y, tdmInteractorReturn *R)
{
  R->change = 0 ;
}

static void
NullEndStroke (tdmInteractor I, tdmInteractorReturn *R)
{
  R->change = 0 ;
}

static void
NullResumeEcho (tdmInteractor I, tdmInteractorRedrawMode redrawmode)
{
}

static void
NullEchoFunc (tdmInteractor I, void *u, float m[4][4], int f)
{
}

/*
 *  Creation functions
 */

tdmInteractor
_dxfCreateRotationInteractor(tdmInteractorWin W,
			     tdmEchoType E, tdmRotateModel M)
{
  register tdmInteractor I ;

  ENTRY(("_dxfCreateRotationInteractor(0x%x, 0x%x, 0x%x)",
	 W, E, M));

  if (W && (I = _dxfAllocateInteractor (W, sizeof(tdmRotateData)))) {
    DEFDATA(I,tdmRotateData) ;
    DEFPORT(I_PORT_HANDLE) ;
    int u, v, echoRadius ;
    
    /* instance initial interactor methods */
    FUNC(I, DoubleClick) = NullDoubleClick ;
    FUNC(I, StartStroke) = StartRotateStroke ;
    FUNC(I, EndStroke) = EndStroke ;
    FUNC(I, ResumeEcho) = ResumeEcho ;
    FUNC(I, Destroy) = Destroy ;
    FUNC(I, restoreConfig) = RestoreConfig ;
    
    switch (M)
      {
      case tdmZTwirl:
	FUNC(I, StrokePoint) = Twirl ;
	break ;
      case tdmXYPlaneRoll:
      default:
	FUNC(I, StrokePoint) = PlaneRollWithEcho ;
	break ;
      }
    
    switch (E)
      {
      case tdmGlobeEcho:
	FUNC(I, EchoFunc) = EFUNCS(DrawGlobe) ;
	FUNC(I, Config) = globeConfig ;
	echoRadius = GLOBERADIUS ;
	
	/* globe is LL corner: window has no effect on size or placement */
	PDATA(vllx) = PDATA(vlly) = GLOBEOFFSET ;
	PDATA(vurx) = PDATA(vury) = GLOBEOFFSET + 2*GLOBERADIUS ;
	
	PDATA(foreground) = (void *) 1 ;
	PDATA(background) = (void *) 1 ;
	break ;
	
      case tdmGnomonEcho:
	FUNC(I, EchoFunc) = EFUNCS(DrawGnomon) ;
	FUNC(I, Config) = gnomonConfig ;
	echoRadius = GNOMONRADIUS ;
	
	PDATA(foreground) = (void *) 1 ;
	PDATA(background) = (void *) 1 ;
	break ;
	
      default:
      case tdmNullEcho:
	FUNC(I, EchoFunc) = NullEchoFunc ;
	FUNC(I, Config) = gnomonConfig ;
	echoRadius = 0 ;
	
	PDATA(foreground) = (void *) 0 ;
	PDATA(background) = (void *) 0 ;
      }

    FUNC(I, KeyStruck) = _dxfNullKeyStruck;

    
    /* allocate background save-under */
    if (PDATA(background))
      PDATA(background) =
	_dxf_ALLOCATE_PIXEL_ARRAY
	  (PORT_CTX, (2*echoRadius +1), (2*echoRadius +1)) ;

    /* allocate foreground + background image for quick restoration */
    if (PDATA(foreground))
      PDATA(foreground) =
	_dxf_ALLOCATE_PIXEL_ARRAY
	  (PORT_CTX, (2*echoRadius +1), (2*echoRadius +1)) ;

    /* initial draw is into both buffers */
    PDATA(redrawmode) = tdmBothBufferDraw ;
    
    /* initially invisible, no font defined */
    PDATA(visible) = 0 ;
    PDATA(font) = -1 ;
    
    EXIT(("I = 0x%x",I));
    return I ;
  }

  EXIT(("ERROR"));
  return 0 ;
}


tdmInteractor
_dxfCreateViewRotationInteractor (tdmInteractorWin W,
				  tdmViewEchoFunc E, tdmRotateModel M,
				  void *udata)
{
  /*
   *  Initialize and return a handle to a view rotation interactor.
   *  The echo function is supplied by the application.
   */

  register tdmInteractor I ;

  ENTRY(("_dxfCreateViewRotationInteractor(0x%x, 0x%x, 0x%x, 0x%x)",
	 W, E, M, udata));

  if (W && (I = _dxfAllocateInteractor (W, sizeof(tdmRotateData))))
    {
      DEFDATA(I,tdmRotateData) ;

      /* instance initial interactor methods */
      FUNC(I, StartStroke) = StartViewStroke ;
      FUNC(I, DoubleClick) = NullDoubleClick ;
      FUNC(I, EndStroke) = EndStroke ;
      FUNC(I, ResumeEcho) = NullResumeEcho ;
      FUNC(I, EchoFunc) = E ;
      FUNC(I, Destroy) = _dxfDeallocateInteractor ;
      FUNC(I, Config) = viewerConfig ;
      FUNC(I, restoreConfig) = NullFunction ;
      
      switch (M)
	{
	case tdmXYPlaneRoll:
	  FUNC(I, StrokePoint) = ViewRoll ;
	  break ;
	case tdmZTwirl:
	default:
	  FUNC(I, StrokePoint) = Twirl ;
	  break ;
	}

      /* copy pointer to user data */
      UDATA(I) = udata ;

      EXIT((""));
      return I ;
    }
  else
      EXIT(("ERROR"));
      return 0 ;
}

void
_dxfRotateInteractorVisible (tdmInteractor I)
{
  ENTRY(("_dxfRotateInteractorVisible(0x%x)", I));

  if (I)
    {
      DEFDATA(I,tdmRotateData) ;
      PDATA(visible) = 1 ;
    }

  EXIT((""));
}

void 
_dxfRotateInteractorInvisible (tdmInteractor I)
{
  ENTRY(("_dxfRotateInteractorInvisible(0x%x)", I));

  if (I)
    {
      DEFDATA(I,tdmRotateData) ;
      PDATA(visible) = 0 ;
    }

  EXIT((""));
}

/*
 *  Method implementations
 */

static void 
StartRotateStroke (tdmInteractor I, int x, int y, int btn, int s)
{
  DEFDATA(I,tdmRotateData) ;

  ENTRY(("StartRotateStroke(0x%x, %d, %d, %d)", I, x, y, btn));

  /* set up hardware, stay in that configuration until EndStroke */
  CALLFUNC(I,Config) (I, &PDATA(displaymode), &PDATA(buffermode),
		      tdmBackBufferDraw) ;

  /* initialize start matrix to current view rotation */
  COPYMATRIX (PDATA(strXfm), CDATA(viewXfm)) ;
  extractViewRot(PDATA(strXfm)) ;
  StartStroke (I, x, y, btn, s) ;

  EXIT((""));
}


static void 
StartViewStroke (tdmInteractor I, int x, int y, int btn, int s)
{
  DEFDATA(I,tdmRotateData) ;

  ENTRY(("StartViewStroke(0x%x, %d, %d, %d)", I, x, y, btn));

  /* set up hardware, stay in that configuration until EndStroke */
  CALLFUNC(I,Config) (I, &PDATA(displaymode), &PDATA(buffermode),
		      tdmBackBufferDraw) ;

  /* initialize start matrix to current view matrix */
  COPYMATRIX (PDATA(strXfm), CDATA(viewXfm)) ;
  StartStroke (I, x, y, btn, s) ;

  EXIT((""));
}


static void 
StartStroke (tdmInteractor I, int x, int y, int btn, int s)
{
  DEFDATA(I,tdmRotateData) ;
  register float dx, dy ;

  ENTRY(("StartStroke(0x%x, %d, %d, %d)", I, x, y, btn));

  /* transform (x,y) to echo coordinate system */
  y = YFLIP(y) ;

  CDATA(xlast) = x ;
  CDATA(ylast) = y ;

  dx = x - PDATA(gx) ;
  dy = y - PDATA(gy) ;

  /* initialize incremental matrix */
  COPYMATRIX (PDATA(incXfm), identity) ;

  /* initialize appropriate stroke processor */
  if (FUNC(I, StrokePoint) == Twirl)
      InitTwirlStroke (I, dx, dy) ;
  else
      InitRollStroke (I, x, y) ;

  EXIT((""));
}


static void 
InitTwirlStroke (tdmInteractor I, float dx, float dy)
{
  /*
   *  Initialize twirling (rotation about Z axis).
   */
  double a ;
  DEFDATA(I,tdmRotateData) ;

  ENTRY(("InitTwirlStroke(0x%x, %f, %f)", I, dx, dy));

  /* compute basis vectors of rotated coordinate system */
  a = sqrt ((double)dx * (double)dx + (double)dy * (double)dy) ;
  dx /= a ;
  dy /= a ;
  
  PDATA(xbasis)[0] =  dx ;
  PDATA(xbasis)[1] =  dy ;
  PDATA(ybasis)[0] = -dy ;
  PDATA(ybasis)[1] =  dx ;

  /* initialize current matrix */
  COPYMATRIX (PDATA(newXfm), identity) ;

  EXIT((""));
}


static void 
InitRollStroke (tdmInteractor I, int x, int y)
{
  /*
   *  Initialize rolling (XY rotation) mode.
   */
  DEFDATA(I,tdmRotateData) ;
  double dx, dy ;

  ENTRY(("InitRollStroke(0x%x, %d, %d)", I, x, y));

  /* initialize current matrix */
  COPYMATRIX (PDATA(newXfm), identity) ;

  EXIT((""));
}


static void 
EndStroke (tdmInteractor I, tdmInteractorReturnP R)
{
  /*
   *  End stroke and return appropriate info.
   */
  DEFDATA(I,tdmRotateData) ;

  ENTRY(("EndStroke(0x%x, 0x%x)", I, R));

  if (PDATA(rotation_update))
    {
      /* return relative rotation transform */
      MULTMATRIX (R->matrix, PDATA(incXfm), PDATA(newXfm)) ;

      if (CDATA(view_coords_set))
	{
	  /* return `to', `from', `up', and view matrix expressing them */
	  PRINT(("RotEndStroke: current viewXfm:"));
	  MPRINT(CDATA(viewXfm)) ;
	  
	  _dxfRenormalizeView(CDATA(viewXfm)) ;
	  PRINT(("RotEndStroke: renormalized viewXfm:"));
	  MPRINT(CDATA(viewXfm)) ;
	  
	  R->from[0] = CDATA(to[0]) + CDATA(vdist)*CDATA(viewXfm[0][2]) ;
	  R->from[1] = CDATA(to[1]) + CDATA(vdist)*CDATA(viewXfm[1][2]) ;
	  R->from[2] = CDATA(to[2]) + CDATA(vdist)*CDATA(viewXfm[2][2]) ;

	  R->to[0] = CDATA(to[0]) ;
	  R->to[1] = CDATA(to[1]) ;
	  R->to[2] = CDATA(to[2]) ;
	  
	  R->up[0] = CDATA(viewXfm[0][1]) ;
	  R->up[1] = CDATA(viewXfm[1][1]) ;
	  R->up[2] = CDATA(viewXfm[2][1]) ;

	  R->dist = CDATA(vdist) ;

	  if (FUNC(I, StrokePoint) == ViewRoll)
	    {
	      /* return view matrix directly */
	      COPYMATRIX(R->view, CDATA(viewXfm)) ;
	    }
	  else
	    {
	      /* compute matrix that orbits `from' point about `to' point */
	      orbitFrom (R->matrix, PDATA(strXfm), CDATA(to), CDATA(vdist)) ;
	      MULTMATRIX(R->view, PDATA(strXfm), R->matrix) ;
	    }
	}
      R->reason = tdmROTATION_UPDATE ;
      R->change = 1 ;
    }
  else
      /* no change */
      R->change = 0 ;

  PDATA(rotation_update) = 0 ;
  CALLFUNC(I,restoreConfig) (I, PDATA(displaymode), PDATA(buffermode),
			     tdmBackBufferDraw) ;

  EXIT((""));
}


static void 
Destroy (tdmInteractor I)
{
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("Destroy(0x%x)", I));

  if (PDATA(background))
      _dxf_FREE_PIXEL_ARRAY (PORT_CTX, PDATA(background)) ;

  if (PDATA(foreground))
      _dxf_FREE_PIXEL_ARRAY (PORT_CTX, PDATA(foreground)) ;

  _dxfDeallocateInteractor(I) ;

  EXIT((""));
}


/*
 *  Z rotation model
 */

static void
Twirl (tdmInteractor I, int x, int y, int flag, int state)
{
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;
  double a ;
  float c, s, dx, dy, tmp[4][4] ;

  ENTRY(("Twirl(0x%x, %d, %d)", I, x, y));

  y = YFLIP(y) ;

  if (CDATA(xlast) == x && CDATA(ylast) == y)
    {
      EXIT(("xlast and ylast the same"));
      return ;
    }
  else
    {
      CDATA(xlast) = x ;
      CDATA(ylast) = y ;
    }

  dx = x - PDATA(gx) ;
  dy = y - PDATA(gy) ;

  a = sqrt ((double)dx * (double)dx + (double)dy * (double)dy) ;
  if (a < 1.0) {
    EXIT(("a < 1.0"));
    return ;
  }

  /*
   *  Compute sine and cosine of rotation matrix relative to the first point
   *  of this stroke.
   */

  s = (PDATA(ybasis)[0]*dx + PDATA(ybasis)[1]*dy) / a ;
  c = (PDATA(xbasis)[0]*dx + PDATA(xbasis)[1]*dy) / a ;

  PDATA(newXfm)[0][0] =  c ;
  PDATA(newXfm)[0][1] =  s ;
  PDATA(newXfm)[0][2] =  0 ;
  PDATA(newXfm)[0][3] =  0 ;

  PDATA(newXfm)[1][0] = -s ;
  PDATA(newXfm)[1][1] =  c ;
  PDATA(newXfm)[1][2] =  0 ;
  PDATA(newXfm)[1][3] =  0 ;

  PDATA(newXfm)[2][0] =  0 ;
  PDATA(newXfm)[2][1] =  0 ;
  PDATA(newXfm)[2][2] =  1 ;
  PDATA(newXfm)[2][3] =  0 ;

  PDATA(newXfm)[3][0] =  0 ;
  PDATA(newXfm)[3][1] =  0 ;
  PDATA(newXfm)[3][2] =  0 ;
  PDATA(newXfm)[3][3] =  1 ;
  
  MULTMATRIX (tmp, PDATA(incXfm), PDATA(newXfm)) ;
  MULTMATRIX (CDATA(viewXfm), PDATA(strXfm), tmp) ;
  _dxf_LOAD_MATRIX (PORT_CTX, CDATA(viewXfm)) ;
  CDATA(view_state)++ ;

  /* update software matrix stack shadow */
  _dxfLoadViewMatrix (CDATA(stack), CDATA(viewXfm)) ;
  
  /* call echo function */
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 0) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 1) ;

  /* redraw any auxiliary echos */
  tdmResumeEcho (AUX(I), tdmAuxEchoMode) ;

  PDATA(rotation_update) = 1 ;

  EXIT((""));
}


/*
 *  XY rotation models
 */

static int
PlaneRoll (tdmInteractor I, int x, int y, float rot[4][4])
{
  /*
   *  Pointer is attached to an infinite plane parallel to XY, which rolls
   *  over a virtual sphere.  If we bail out, return 0.
   */

  DEFDATA(I,tdmRotateData) ;
  register float dx, dy, t, s, c ;
  float tmp[4][4] ;
  double a ;

  ENTRY(("PlaneRoll(0x%x, %d, %d, 0x%x)", I, x, y, rot));

  y = YFLIP(y) ;

  dx = CDATA(xlast) - x ;
  dy = CDATA(ylast) - y ;

  if (dx == 0 && dy == 0)
    {
      EXIT(("no change"));
      return 0 ;
    }
  else
    {
      CDATA(xlast) = x ;
      CDATA(ylast) = y ;
    }

  /* rotate delta vector by pi/2, use as axis of rotation */
  t = dy ;
  dy = -dx ;
  dx = t ;

  /* normalize */
  if ((a = sqrt ((double)dx * (double)dx + (double)dy * (double)dy)) < 1.0) {
    EXIT(("a < 1.0"));
    return 0 ;
  }

  dx /= a ;
  dy /= a ;

  /* convert pixels to radians */
  a *= 1.0/(float)PDATA(gradius) ;

  /* compute the matrix which rotates about the vector [dx dy 0] */
  s = (float) sin(a) ;
  c = (float) cos(a) ;
  t = 1.0 - c ;
  ROTXY (rot, dx, dy, s, c, t) ;

  /* accumulate new relative and incremental rotation matrices */
  MULTMATRIX (tmp, PDATA(newXfm), rot) ;
  COPYMATRIX (PDATA(newXfm), tmp) ;
  MULTMATRIX (rot, PDATA(incXfm), PDATA(newXfm)) ;

  EXIT(("1"));
  return 1 ;
}


static void
PlaneRollWithEcho (tdmInteractor I, int x, int y, int flag, int s)
{
  float rot[4][4] ;
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("PlaneRollWithEcho(0x%x, %d, %d)", I, x, y));
  
  /* apply plane rolling model */
  if (!PlaneRoll (I, x, y, rot))
    {
      EXIT(("PlaneRoll returns 0"));
      return ;
    }

  /* accumulate start transform */
  MULTMATRIX (CDATA(viewXfm), PDATA(strXfm), rot) ;

  /* load new view matrix, erase old echo, draw new echo */
  _dxf_LOAD_MATRIX (PORT_CTX, CDATA(viewXfm)) ;
  _dxfLoadViewMatrix (CDATA(stack), CDATA(viewXfm)) ;
  CDATA(view_state)++ ;
  
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 0) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 1) ;

  /* redraw any auxiliary echo */
  tdmResumeEcho (AUX(I), tdmAuxEchoMode) ;

  PDATA(rotation_update) = 1 ;

  EXIT((""));
}


static void
ViewRoll (tdmInteractor I, int x, int y, int flag, int s)
{
  float rot[4][4], from[3], zaxis[3], Near, Far ;
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("ViewRoll(0x%x, %d, %d)", I, x, y));

  /* apply plane rotation model */
  if (!PlaneRoll (I, x, y, rot))
    {
      EXIT(("PlaneRoll returns 0"));
      return ;
    }

  if (CDATA(view_coords_set))
      /* orbit the `from' point about the `to' point */
      orbitFrom(rot, PDATA(strXfm), CDATA(to), CDATA(vdist)) ;

  /* rotate the view */
  MULTMATRIX (CDATA(viewXfm), PDATA(strXfm), rot) ;

  /* get the new clip planes */
  GET_VC_ORIGIN(CDATA(viewXfm), from) ;
  GET_VIEW_DIRECTION(CDATA(viewXfm), zaxis) ;
  _dxfGetNearFar(CDATA(projection), CDATA(w),
		CDATA(projection)? CDATA(fov): CDATA(width),
		from, zaxis, CDATA(box), &Near, &Far);

  /* setting new clip planes requires creating a new projection matrix */
  if (CDATA(projection))
    {
      _dxfSetProjectionInfo(CDATA(stack), 1, CDATA(fov), CDATA(aspect),
			   Near, Far);
      _dxf_SET_PERSPECTIVE_PROJECTION(PORT_CTX, CDATA(fov), CDATA(aspect),
				     Near, Far);
    }
  else
    {
      _dxfSetProjectionInfo(CDATA(stack), 0, CDATA(width), CDATA(aspect),
			   Near, Far);
      _dxf_SET_ORTHO_PROJECTION(PORT_CTX, CDATA(width), CDATA(aspect),
			       Near, Far); 
    }

  /* load the new view matrix */
  _dxf_LOAD_MATRIX (PORT_CTX, CDATA(viewXfm)) ;
  _dxfLoadViewMatrix (CDATA(stack), CDATA(viewXfm)) ;
  CDATA(view_state)++ ;

  /* erase and draw the echo */
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 0) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 1) ;

  PDATA(rotation_update) = 1 ;

  EXIT((""));
}


static void
orbitFrom (float R[4][4], float current[4][4], float to[3], float vdist)
{
  /*
   *  To rotate the view about the `to' point, we in effect rotate the
   *  `from' point, the origin of the current view coordinate system.
   *  This keeps the `to' point centered in the view.
   *
   *  The columns of `R' are the basis vectors of the new view rotation
   *  coordinate system, expressed in terms of the current view rotation
   *  coordinate system (ie, `R' is a relative as opposed to an absolute
   *  rotation).  `vdist' is the distance between the `from' and `to'
   *  points, so the new `from-to' vector (expressed in current view
   *  rotation coordinates) is the 3rd column of `R' scaled by `vdist'.
   *
   *  The `to' point is then rotated by the current view rotation matrix
   *  and then added to the `from-to' vector.  The negative of the result
   *  replaces the 4th (translation) row of the current view matrix.  This
   *  is the same matrix that would be obtained by rotating the `from-to'
   *  vector into world coordinates, adding the `to' point in world
   *  coordinates, expressing the result as a translation matrix, and
   *  pre-concatenating it with current view rotation.
   */

  float fromVectVC[3], toVC[3] ;
  
  ENTRY(("orbitFrom(0x%x, 0x%x, 0x%x, %f)",
	 R, current, to, vdist));
  
  fromVectVC[0] = R[0][2] * vdist ;
  fromVectVC[1] = R[1][2] * vdist ;
  fromVectVC[2] = R[2][2] * vdist ;
  
  toVC[0] = to[0] * current[0][0] +
            to[1] * current[1][0] +
	    to[2] * current[2][0] ;
  toVC[1] = to[0] * current[0][1] +
            to[1] * current[1][1] +
	    to[2] * current[2][1] ;
  toVC[2] = to[0] * current[0][2] +
            to[1] * current[1][2] +
	    to[2] * current[2][2] ;
  
  current[3][0] = -(fromVectVC[0] + toVC[0]) ;
  current[3][1] = -(fromVectVC[1] + toVC[1]) ;
  current[3][2] = -(fromVectVC[2] + toVC[2]) ;

  EXIT((""));
}

  
/*
 *  Configuration and drawing routines 
 */

static void
extractViewRot (float M[4][4])
{
  /*
   *  DXExtract view rotation from M.
   */
  ENTRY(("extractViewRot(0x%x)", M));
  
  M[0][3] =  0 ; M[1][3] =  0 ; M[2][3] =  0 ; 
  M[3][0] =  0 ; M[3][1] =  0 ; M[3][2] =  0 ; M[3][3] =  1 ;

  EXIT((""));
}


static void
ResumeEcho (tdmInteractor I, tdmInteractorRedrawMode redrawmode)
{
  /*
   *  Redraw interactor I
   */
  
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("ResumeEcho(0x%x, 0x%x)", I, redrawmode));

  if (!PDATA(visible))
    {
      /* invisible: draw nothing */
      EXIT(("invisible"));
      return ;
    }

  if (FUNC(I, EchoFunc) == NullEchoFunc)
    {
      PRINT(("null echo function, drawing aux echos"));
      tdmResumeEcho (AUX(I), redrawmode) ;
      EXIT((""));
      return ;
    }
  
  if (PDATA(foreground) &&
      redrawmode == tdmAuxEchoMode &&
      PDATA(foregroundView) == CDATA(view_state))
    {
      /*
       *  Quick restoration.  This isn't just an optimization: the cursor
       *  interactor uses the gnomon as an auxiliary echo, and the GL
       *  context must not be altered during cursor interaction mode.
       */
      _dxf_WRITE_BUFFER (PORT_CTX,
			PDATA(illx), PDATA(illy), PDATA(iurx), PDATA(iury),
			PDATA(foreground)) ;
      PRINT(("restoration via blit, drawing aux echos"));
      tdmResumeEcho (AUX(I), tdmAuxEchoMode) ;
      EXIT((""));
      return ;
    }
      
  /* configure */
  CALLFUNC(I,Config) (I, &PDATA(displaymode), &PDATA(buffermode), redrawmode) ;

  /* get view rotation */
  COPYMATRIX (PDATA(strXfm), CDATA(viewXfm)) ;
  extractViewRot(PDATA(strXfm)) ;

  if (PDATA(background) &&
      (redrawmode == tdmBothBufferDraw || redrawmode == tdmFrontBufferDraw))
      /* get new image background */
      _dxf_READ_BUFFER (PORT_CTX,
			PDATA(illx), PDATA(illy), PDATA(iurx), PDATA(iury),
			PDATA(background)) ;

  /* draw echo on top of background image */
  _dxf_LOAD_MATRIX (PORT_CTX, PDATA(strXfm)) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), PDATA(strXfm), 0) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), PDATA(strXfm), 1) ;

  if (PDATA(foreground) && 
      (redrawmode == tdmBothBufferDraw || redrawmode == tdmFrontBufferDraw))
    {
      /* get new image background + foreground for quick restoration */
      _dxf_READ_BUFFER (PORT_CTX,
		       PDATA(illx), PDATA(illy), PDATA(iurx), PDATA(iury),
		       PDATA(foreground)) ;
      PDATA(foregroundView) = CDATA(view_state) ;
    }
  
  /* restore configuration */
  CALLFUNC(I,restoreConfig) (I, PDATA(displaymode), PDATA(buffermode),
			     redrawmode) ;

  PRINT(("echo redrawn, drawing aux echos"));
  if (redrawmode == tdmBothBufferDraw)
    {
      PRINT(("tdmBothBufferDraw -> tdmFrontBufferDraw"));
      tdmResumeEcho (AUX(I), tdmFrontBufferDraw) ;
    }
  else
      tdmResumeEcho (AUX(I), redrawmode) ;

  EXIT((""));
}


static void 
globeConfig (tdmInteractor I,
	     int *CurrentDisplayMode, int *CurrentBufferMode,
	     tdmInteractorRedrawMode redrawMode) 
{
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("globeConfig(0x%x, 0x%x, 0x%x, 0x%x)",
	 I, CurrentDisplayMode, CurrentBufferMode, redrawMode));

  PDATA(redrawmode) = redrawMode ;

  if (redrawMode == tdmAuxEchoMode)
    {
      /* frame buffer configuration tasks already done */
      PRINT(("redraw mode tdmAuxEchoMode, skipping buffer configuration"));
      _dxfPushViewport (CDATA(stack)) ;
      _dxfSetViewport (CDATA(stack), (int) PDATA(vllx), (int) PDATA(vurx),
		      (int) PDATA(vlly), (int) PDATA(vury)) ;
      _dxf_SET_VIEWPORT (PORT_CTX, (int) PDATA(vllx), (int) PDATA(vurx),
			(int) PDATA(vlly), (int) PDATA(vury)) ;

      _dxfPushViewMatrix(CDATA(stack)) ;
      _dxf_SET_ORTHO_PROJECTION (PORT_CTX, 2, 1, 1, -1) ;

      EXIT((""));
      return ;
    }

  /* turn off Z buffer */
  CDATA(zbuffer) = _dxf_GET_ZBUFFER_STATUS(PORT_CTX) ;
  if (CDATA(zbuffer))
      _dxf_SET_ZBUFFER_STATUS (PORT_CTX, 0) ;

  /* configure the frame buffer */
  _dxf_BUFFER_CONFIG (PORT_CTX, CDATA(image),
		     0, CDATA(h)-CDATA(ih), CDATA(iw)-1, CDATA(h)-1, 
		     CurrentDisplayMode, CurrentBufferMode, redrawMode) ;

  /* get origin of window and dimensions of screen */
  _dxf_GET_WINDOW_ORIGIN (PORT_CTX, &CDATA(ox), &CDATA(oy)) ;
  _dxf_GET_MAXSCREEN (PORT_CTX, &CDATA(xmaxscreen), &CDATA(ymaxscreen)) ;

  /* virtual sphere radius is half of smallest window dimension */
  if (CDATA(h) > CDATA(w))
    {
      PDATA(gradius) = CDATA(w)/2 ;
      PDATA(gx) = PDATA(gradius) ;
      PDATA(gy) = PDATA(gradius) + (CDATA(h) - CDATA(w))/2 ;
    }
  else
    {
      PDATA(gradius) = CDATA(h)/2 ;
      PDATA(gy) = PDATA(gradius) ;
      PDATA(gx) = PDATA(gradius) + (CDATA(w) - CDATA(h))/2 ;
    }

  PDATA(g2) = PDATA(gradius)*PDATA(gradius) ;

  /* push and load new viewport */
  _dxfPushViewport (CDATA(stack)) ;
  _dxfSetViewport (CDATA(stack), (int) PDATA(vllx), (int) PDATA(vurx),
		  (int) PDATA(vlly), (int) PDATA(vury)) ;
  _dxf_SET_VIEWPORT (PORT_CTX, (int) PDATA(vllx), (int) PDATA(vurx),
		    (int) PDATA(vlly), (int) PDATA(vury)) ;

  /* get current viewing matrix and push it */
  _dxfGetViewMatrix (CDATA(stack), CDATA(viewXfm)) ;
  _dxfPushViewMatrix(CDATA(stack)) ;

  /* load new projection */
  _dxf_SET_ORTHO_PROJECTION (PORT_CTX, 2, 1, 1, -1) ;

  /* globe image background coordinates for lrectread() and lrectwrite() */
  PDATA(illx) = XSCREENCLIP(PDATA(vllx)) ;
  PDATA(illy) = YSCREENCLIP(PDATA(vlly)) ;
  PDATA(iurx) = XSCREENCLIP(PDATA(vurx)) ;
  PDATA(iury) = YSCREENCLIP(PDATA(vury)) ;

  /* set solid area pattern */
  _dxf_SET_SOLID_FILL_PATTERN (PORT_CTX) ;

  EXIT((""));
}


static void
gnomonConfig (tdmInteractor I, int *c, int *d,
	      tdmInteractorRedrawMode redrawMode)
{
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("gnomonConfig(0x%x, 0x%x, 0x%x, 0x%x)",
	 I, c, d, redrawMode));

  /* gnomon position depends upon window size */
  PDATA(gradius) = GNOMONRADIUS ;
  PDATA(gx) = CDATA(w) - (GNOMONOFFSET+GNOMONRADIUS) ;
  PDATA(gy) = GNOMONOFFSET+GNOMONRADIUS ;
  PDATA(g2) = GNOMONRADIUS*GNOMONRADIUS ;
  PDATA(vllx) = CDATA(w) - (GNOMONOFFSET + 2*GNOMONRADIUS) ;
  PDATA(vlly) = GNOMONOFFSET ;
  PDATA(vurx) = CDATA(w) - GNOMONOFFSET ;
  PDATA(vury) = GNOMONOFFSET + 2*GNOMONRADIUS ;
      
  /* gnomon image background coordinates for lrectread() and lrectwrite() */
  PDATA(illx) = XSCREENCLIP(PDATA(vllx)) ;
  PDATA(illy) = YSCREENCLIP(PDATA(vlly)) ;
  PDATA(iurx) = XSCREENCLIP(PDATA(vurx)) ;
  PDATA(iury) = YSCREENCLIP(PDATA(vury)) ;

  PDATA(redrawmode) = redrawMode ;

  if (redrawMode == tdmAuxEchoMode)
    {
      PRINT(("redraw mode is tdmAuxEchoMode, skipping configuration"));
      _dxfPushViewport (CDATA(stack)) ;
      _dxfSetViewport (CDATA(stack), (int) PDATA(vllx), (int) PDATA(vurx),
		      (int) PDATA(vlly), (int) PDATA(vury)) ;
      _dxf_SET_VIEWPORT (PORT_CTX, (int) PDATA(vllx), (int) PDATA(vurx),
			(int) PDATA(vlly), (int) PDATA(vury)) ;

      _dxfPushViewMatrix(CDATA(stack)) ;
      _dxf_SET_ORTHO_PROJECTION (PORT_CTX, 2, 1, 1, -1) ;
    }
  else
      globeConfig (I, c, d, redrawMode) ;

  EXIT((""));
}


static void
viewerConfig (tdmInteractor I, int *c, int *d,
	      tdmInteractorRedrawMode redrawMode)
{
  DEFDATA(I,tdmRotateData) ;

  ENTRY(("viewerConfig(0x%x, 0x%x, 0x%x, 0x%x)",
	 I, c, d, redrawMode));

  /* get current view matrix */
  _dxfGetViewMatrix (CDATA(stack), CDATA(viewXfm)) ;

  /* virtual sphere radius is half of smallest window dimension */
  if (CDATA(h) > CDATA(w))
    {
      PDATA(gradius) = CDATA(w)/2 ;
      PDATA(gx) = PDATA(gradius) ;
      PDATA(gy) = PDATA(gradius) + (CDATA(h) - CDATA(w))/2 ;
    }
  else
    {
      PDATA(gradius) = CDATA(h)/2 ;
      PDATA(gy) = PDATA(gradius) ;
      PDATA(gx) = PDATA(gradius) + (CDATA(w) - CDATA(h))/2 ;
    }

  PDATA(g2) = PDATA(gradius)*PDATA(gradius) ;

  EXIT((""));
}


static void
RestoreConfig (tdmInteractor I,
	       int OrigDisplayMode, int OrigBufferMode,
	       tdmInteractorRedrawMode redrawMode)
{
  float M[4][4] ;
  int llx, lly, urx, ury ;
  DEFDATA(I,tdmRotateData) ;
  DEFPORT(I_PORT_HANDLE) ;

  ENTRY(("RestoreConfig(0x%x, %d, %d, 0x%x)",
	 I, OrigDisplayMode, OrigBufferMode, redrawMode));

  /* restore viewport */
  _dxfPopViewport (CDATA(stack)) ;
  _dxfGetViewport (CDATA(stack), &llx, &urx, &lly, &ury) ;
  _dxf_SET_VIEWPORT (PORT_CTX, llx, urx, lly, ury) ;

  if (PDATA(redrawmode) != tdmAuxEchoMode)
    {
      /* restore hardware projection matrix */
      if (CDATA(projection))
	  _dxf_SET_PERSPECTIVE_PROJECTION
	      (PORT_CTX, CDATA(fov), CDATA(aspect),
	       CDATA(Near), CDATA(Far)) ;
      else
	  _dxf_SET_ORTHO_PROJECTION
	      (PORT_CTX, CDATA(width), CDATA(aspect),
	       CDATA(Near), CDATA(Far)) ;

      /* restore frame buffer configuration */
      _dxf_BUFFER_RESTORE_CONFIG
	  (PORT_CTX, OrigDisplayMode, OrigBufferMode, redrawMode) ;

      /* restore zbuffer usage */
      _dxf_SET_ZBUFFER_STATUS (PORT_CTX, CDATA(zbuffer)) ;
    }

  /* restore viewing/modeling hardware matrix */
  _dxfPopViewMatrix(CDATA(stack)) ;
  _dxfGetViewMatrix (CDATA(stack), M) ;
  _dxf_LOAD_MATRIX (PORT_CTX, M) ;

  EXIT((""));
}

#if 0
/***** CAN THIS DEAD CODE BE REMOVED??????? ********/
static void
PointRoll (tdmInteractor I, int x, int y)
{
  /*
   *  DXApply vertical Z projection of pointer onto surface of virtual
   *  sphere and "attach" pointer to it, until pointer moves off echo.
   */
  
  DEFDATA(I,tdmRotateData) ;
  dxMatrix rot ;
  double a, u, v, bx, by, bz ;
  register float dx, dy, t, s, c, cx, sx, cy, sy ;
  float rx, ry, rz ;

  ENTRY(("PointRoll(0x%x, %d, %d)", I, x, y));
  
  y = YFLIP(y) ;

  if (CDATA(xlast) == x && CDATA(ylast) == y) {
    EXIT(("xlast && ylast unchanged"));
    return ;
  } else {
    CDATA(xlast) = x ;
    CDATA(ylast) = y ;
  }

  dx = x - PDATA(gx) ;
  dy = y - PDATA(gy) ;

  if (dx*dx + dy*dy > PDATA(g2))
    {
      /*
       *  Fell off the edge of the world:  concatenate newest matrix onto
       *  incremental accumulation and start twirling.
       */
      loadmatrix(PDATA(newXfm)) ;
      multmatrix(PDATA(incXfm)) ;
      getmatrix(PDATA(incXfm)) ;

      InitTwirlStroke (I, dx, dy) ;
      EXIT(("fell off world"));
      return ;
    }

  bx = (double)dx/(double)PDATA(gradius) ;
  by = (double)dy/(double)PDATA(gradius) ;

  /*
   *  Compute matrix to rotate vector [ax,ay,az] to [bx,by,bz].
   *  These vectors extend from the center of the sphere to the radius.
   */

  if (bx == PDATA(ax) && by == PDATA(ay)) {
    EXIT(("bx and by unchanged"));
    return ;
  }
  
  bz = sqrt (1.0 - (bx*bx + by*by)) ;
  
  /* cross product is the axis of rotation */
  rx = PDATA(ay)*bz - PDATA(az)*by ;
  ry = PDATA(az)*bx - PDATA(ax)*bz ;
  rz = PDATA(ax)*by - PDATA(ay)*bx ;

  /* length of axis is the sine of the angle to rotate */
  s = (float) sqrt ((double)rx*rx + (double)ry*ry + (double)rz*rz) ;

  /* cosine of angle is dot product of vectors */
  c = PDATA(ax)*bx + PDATA(ay)*by + PDATA(az)*bz ;

  /* normalize rotation axis */
  rx /= s ; ry /= s ; rz /= s ;
  
  /* compute matrix which rotates about [rx,ry,rz] */
  t = 1.0 - c ;

  ROTXYZ (rot, rx, ry, rz, s, c, t) ;
  loadmatrix (rot) ;

#ifdef RELATIVEROT
  /* accumulate relative rotation matrix onto transform */
  multmatrix(PDATA(newXfm)) ;
  getmatrix(PDATA(newXfm)) ;

  /* update previous vector */
  PDATA(ax) = bx ;
  PDATA(ay) = by ;
  PDATA(az) = bz ;
#else
  COPYMATRIX (PDATA(newXfm), rot) ;
#endif

  /* accumulate new and incremental transforms onto start transform */
  multmatrix(PDATA(incXfm)) ;
  multmatrix(PDATA(strXfm)) ;

  /* get new view, erase old echo, draw new echo */
  getmatrix(CDATA(viewXfm)) ;
  CDATA(view_state)++ ;
  
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 0) ;
  CALLFUNC(I,EchoFunc) (I, UDATA(I), CDATA(viewXfm), 1) ;

  /* redraw any auxiliary echo */
  tdmResumeEcho (AUX(I), tdmAuxEchoMode) ;
  swapbuffers() ;

  PDATA(rotation_update) = 1 ;

  EXIT((""));
}
#endif
