/* nrframe.c **************************************************************

  NR Library Package - Homogeneous Transformation Frames

  James Trevelyan,  University of Western Australia
  Revision 2   January 1996

**************************************************************************/

/* define V_CHECK to include validity checks on matrices */
#define V_CHECK

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "nrlin.h"

#define True 1
#define False 0
#define X 1
#define Y 2
#define Z 3
#define U 4

#ifdef TEST
extern FILE *outfile;
extern double **F1;
#endif

double **SCR;

double **dframe( void )
{
   return ( dmatrix( 1, 4, 1, 4 ) );
}

double *d4vector( void )
{
   return ( dvector( 1, 4 ) );
}

void chainmult( double **a, double **b )
/* multiply 4*4 matrices a, b into a, using SCR scratch matrix above */
{   
   static int once = 0;

   if ( !once ) {
      SCR = dmatrix( 1,4, 1,4 );
      once = True;
   }
#ifdef V_CHECK
   if ( !valid_dframe( a ) ) 
      nrerror("Invalid 1st matrix: chainmult\n"); 
   if ( !valid_dframe( b ) ) 
      nrerror("Invalid 2nd matrix: chainmult\n"); 
#endif
   dmmult( a, 4, 4, b, 4, 4, SCR );
   dmcopy( SCR, 4, 4, a );
}


void rotframe( double **a, int axis, double theta) /* make a rotation transform */
{
   double ca, sa;
   int i,j;

#ifdef V_CHECK
   if ( !valid_dframe( a ) ) 
      nrerror("Invalid matrix: rotframe\n");
#endif
   ca = cos(theta);
   sa = sin(theta);
   for (i=1; i<=4; i++)
      for (j=1; j<=4; j++) a[i][j] = 0.0;
   switch( axis) {
     case 3:
      a[4][4] = a[3][3] = 1.0;
      a[2][2] = a[1][1] = ca;
      a[1][2] = -sa;
      a[2][1] = sa;
      break;
     case 1:
      a[4][4] = a[1][1] = 1.0;
      a[2][2] = a[3][3] = ca;
      a[2][3] = -sa;
      a[3][2] = sa;
      break;
     case 2:
      a[4][4] = a[2][2] = 1.0;
      a[1][1] = a[3][3] = ca;
      a[3][1] = -sa;
      a[1][3] = sa;
      break;
     default: 
      fprintf(stderr, "rotframe: axis not X, Y or Z\n");
      exit(1);
      break;
   }
}

void transframe( double **a, double dx, double dy, double dz) 
/* make a translation tr'm */
{
   int i,j;

#ifdef V_CHECK
   if ( !valid_dframe( a ) ) 
      nrerror("Invalid matrix: transframe\n");    
#endif
   for (i=1; i<=4; i++)
      for (j=1; j<=4; j++) a[i][j] = 0.0;
   a[4][4] = a[3][3] = 1.0;
   a[2][2] = a[1][1] = 1.0;
   a[1][4] = dx;
   a[2][4] = dy;
   a[3][4] = dz;
}


void orthog( double **a ) /* orthogonalize a frame */
{
   int i, j, k;
   double s;

#ifdef V_CHECK
   if ( !valid_dframe( a ) ) 
      nrerror("Invalid matrix: orthog\n");    
#endif
   /* cross j, k to produce i */
   a[1][1] = a[2][2]*a[3][3] - a[3][2]*a[2][3];
   a[2][1] = a[3][2]*a[1][3] - a[1][2]*a[3][3];
   a[3][1] = a[1][2]*a[2][3] - a[2][2]*a[1][3];

   /* normalize i, k */
   s = sqrt(DSQR(a[1][3]) + DSQR(a[2][3]) + DSQR(a[3][3]));
   for (i=1; i<=3; i++) a[i][3] /= s;
   s = sqrt(DSQR(a[1][1]) + DSQR(a[2][1]) + DSQR(a[3][1]));
   for (i=1; i<=3; i++) a[i][1] /= s;

   /* cross k, i to produce j */
   a[1][2] = a[2][3]*a[3][1] - a[3][3]*a[2][1];
   a[2][2] = a[3][3]*a[1][1] - a[1][3]*a[3][1];
   a[3][2] = a[1][3]*a[2][1] - a[2][3]*a[1][1];

}

void orthogKI( double **a ) /* orthogonalize a frame using K & I vector */
{
   int i, j, k;
   double s;

#ifdef V_CHECK
   if ( !valid_dframe( a ) )
      nrerror("Invalid matrix: orthog\n");
#endif
   /* cross k, i to produce j */
   a[1][2] = a[2][3]*a[3][1] - a[3][3]*a[2][1];
   a[2][2] = a[3][3]*a[1][1] - a[1][3]*a[3][1];
   a[3][2] = a[1][3]*a[2][1] - a[2][3]*a[1][1];

   /* normalize j, k */
   s = sqrt(DSQR(a[1][3]) + DSQR(a[2][3]) + DSQR(a[3][3]));
   for (i=1; i<=3; i++) a[i][3] /= s;
   s = sqrt(DSQR(a[1][2]) + DSQR(a[2][2]) + DSQR(a[3][2]));
   for (i=1; i<=3; i++) a[i][2] /= s;

   /* cross j, k to produce i */
   a[1][1] = a[2][2]*a[3][3] - a[3][2]*a[2][3];
   a[2][1] = a[3][2]*a[1][3] - a[1][2]*a[3][3];
   a[3][1] = a[1][2]*a[2][3] - a[2][2]*a[1][3];

}

void dframeinverse( double **E, double **Ein ) /* form inverse frame */
{
   double a[6], b[6];
   int i, j;

   validate_dvector( a, 1, 4);
   validate_dvector( b, 1, 4);

   for (i=1; i<=3; i++) {
      for (j=1; j<=3; j++) {
         Ein[i][j] = E[j][i];
      }
      Ein[4][i] = 0.0;
      Ein[i][4] = 0.0;
   }
   Ein[4][4] = 1.0;

   dgetcolumn(E, 4, a, 4);
   dmvmult(Ein, 4, 4, a, 4, b);
   for (i=1; i<=3; i++) Ein[i][4] = -b[i];
}


void dcross4 ( double *a, double *b, double *c ) /* cross two 4-vectors */
{
   double x, y, z;

#ifdef V_CHECK
   if ( !valid_d4vector_b( a ) ) 
      nrerror("Invalid vector 1: dcross4\n"); 
   if ( !valid_d4vector_b( b ) ) 
      nrerror("Invalid vector 2: dcross4\n"); 
   if ( !valid_d4vector_b( c ) ) 
      nrerror("Invalid vector 3: dcross4\n"); 
#endif
   x = a[2]*b[3] - a[3]*b[2];
   y = a[3]*b[1] - a[1]*b[3];
   z = a[1]*b[2] - a[2]*b[1];
   c[1] = x; 
   c[2] = y; 
   c[3] = z; 
   c[4] = 0.0;
}

double column_dot( double **G1, int col1, double **G2, int col2 )
/* form vector dot product of two columns (1st 3 elements only) */
{
   int i;
   double sum;

#ifdef V_CHECK
   if ( !valid_dframe( G1 ) ) 
      nrerror("Invalid 1st frame: column_dot\n"); 
   if ( !valid_dframe( G2 ) ) 
      nrerror("Invalid 2nd frame: column_dot\n"); 
#endif
   sum = 0.0;
   for ( i=1; i<=3; i++ ) sum += G1[i][col1]*G2[i][col2];
   return(sum);
}

void make_d_omega( double **G1, double **G2, double *w)
/* find small rotational difference between two frames G1, G2 */
/* ref: Shear Magic 4A.27 p129 */
{
#ifdef V_CHECK
   if ( !valid_dframe( G1 ) ) 
      nrerror("Invalid 1st frame: make_d_omega\n");
   if ( !valid_dframe( G2 ) ) 
      nrerror("Invalid 2nd frame: make_d_omega\n");   
#endif
   w[1] = column_dot( G1, 3, G2, 2 );
   w[2] = column_dot( G1, 1, G2, 3 );
   w[3] = column_dot( G1, 2, G2, 1 );
   w[4] = 0.0;
}

void compare_link_frames( double **G1, double **G2, double *wp, double *wr, double *ww )
{
   double wrot[6], z1[6], z2[6], zz[6];

   validate_dvector(wrot, 1, 4);
   validate_dvector(z1, 1, 4);
   validate_dvector(z2, 1, 4);
   validate_dvector(zz, 1, 4);

#ifdef V_CHECK
   if ( !valid_dframe( G1 ) ) 
      nrerror("Invalid 1st frame: compare_link_frames\n");    
   if ( !valid_dframe( G2 ) ) 
      nrerror("Invalid 2nd frame: compare_link_frames\n");    
#endif
   *wp = *wr = *ww = 0.0;
   dgetcolumn( G1, Z, z1, 4 ); /* z axis comparison by cross product */
   dgetcolumn( G2, Z, z2, 4 );
   dcross4( z1, z2, zz );
   *wr = dvmag( zz, 3 );

   dgetcolumn( G1, U, z1, 4 ); /* origin comparison */
   dgetcolumn( G2, U, z2, 4 );
   dvsub( z1, 4, z2, zz );
   *wp = dvmag( zz, 3 );

   /*  printf("wp, wr, ww: %8.4lf %8.4lf %8.4lf\n", *wp, *wr, *ww ); */
}

void compare_frames( double **G1, double **G2, double *wp, double *wr, double *ww )
{
   double wrot[6], z1[6], z2[6], zz[6];

   validate_dvector(wrot, 1, 4);
   validate_dvector(z1, 1, 4);
   validate_dvector(z2, 1, 4);
   validate_dvector(zz, 1, 4);

#ifdef V_CHECK
   if ( !valid_dframe( G1 ) ) 
      nrerror("Invalid 1st frame: compare_frames\n"); 
   if ( !valid_dframe( G2 ) ) 
      nrerror("Invalid 2nd frame: compare_frames\n"); 
#endif
   *wp = *wr = *ww = 0.0;
   make_d_omega( G1, G2, wrot );   
   *wr = dvmag( wrot, 3 );
   *wp = sqrt( DSQR(G1[X][U] - G2[X][U]) 
      + DSQR(G1[Y][U] - G2[Y][U]) 
      + DSQR(G1[Z][U] - G2[Z][U]) );
   *ww = sqrt( DSQR(*wr) + DSQR(*wp) );
   /*  printf("wp, wr, ww: %8.4lf %8.4lf %8.4lf\n", *wp, *wr, *ww ); */
}

/*------------------------------------------------------------*/
void FindAxis(double **T1, double **T2, double *axis,
                double *sphi, double *cphi,int *twitching)
{
   /*  Routine to find the axis of rotation between two frames and the angle between them.
   */

   static double **FF;
   static int done_once = False;
   double ca[6],cb[6],cc[6],cd[6]; /*   workspace vectors   */
   double da[6],db[6],dc[6],dd[6]; /*   workspace vectors   */
   double d1, d2, d3, dmax;

   validate_dvector(ca, 1, 4);
   validate_dvector(cb, 1, 4);
   validate_dvector(cc, 1, 4);
   validate_dvector(cd, 1, 4);
   validate_dvector(da, 1, 4);
   validate_dvector(db, 1, 4);
   validate_dvector(dc, 1, 4);
   validate_dvector(dd, 1, 4);

   if (!done_once) {
      FF = dmatrix( 1, 4, 1, 4 );
      done_once = True;
   }

#ifdef V_CHECK
   if ( !valid_dframe( T1 ) ) 
      nrerror("Invalid 1st frame: FindAxis\n");   
   if ( !valid_dframe( T2 ) ) 
      nrerror("Invalid 2nd frame: FindAxis\n");   
   if ( !valid_d4vector_b( axis ) )
      nrerror("Invalid axis vector: FindAxis\n"); 
#endif

   *twitching = False;

   /*  Find axis of rotation */

   dmsub(T2, 4, 4, T1, FF);             /*  Difference of transforms    */
   dgetcolumn(FF, X, ca, 4);
   dgetcolumn(FF, Y, cb, 4);
   dgetcolumn(FF, Z, cc, 4);
   dcross4( ca, cb, da );
   dcross4( cb, cc, db );
   dcross4( cc, ca, dc );
   d1 = dvmnorm( da, 3 );
   d2 = dvmnorm( db, 3 );
   d3 = dvmnorm( dc, 3 );
   if ( d1 < d2 ) {
      if ( d2 < d3 ) {
         dvcopy( dc, 4, axis );
         dmax = d3;
      } 
      else {
         dvcopy( db, 4, axis );
         dmax = d2;
      }
   } 
   else {
      if ( d1 < d3 ) {
         dvcopy( dc, 4, axis );
         dmax = d3;
      } 
      else {
         dvcopy( da, 4, axis );
         dmax = d1;
      }
   }
   /*    printf("dmax = %12.6lf\n", dmax); */
   if( dmax < 0.000001 ) {                  /* there is no rotation, or we have a problem  */
      d1 = column_dot( T1, X, T2, X);
      d2 = column_dot( T1, Y, T2, Y);
      d3 = column_dot( T1, Z, T2, Z);
      if( d1<0.5 || d2<0.5 || d3<0.5 ) { /* problem */
         fprintf( stderr, "FindAxis: Frames opposed to each other\n");
         dgetcolumn(T1, Z, axis, 4);      /* Set axis to k vector and angle */
         *cphi = 1.0;                     /* to zero.  */
         *sphi = 0.0;
         return;
      } 
      else {                             /* Orientation change is small */
         *twitching = True;
         ca[1] = column_dot( T1, 3, T2, 2 );
         ca[2] = column_dot( T1, 1, T2, 3 );
         ca[3] = column_dot( T1, 2, T2, 1 );
         ca[4] = 0.0;
         d1 = dvmag( ca, 3 );
         if ( d1 > 0.00000000000001 )d1 = dvmnorm( ca, 3 );
         printf("Twitch\n");
         dvcopy( ca, 4, axis );
         *sphi = d1;
         *cphi = sqrt(1.0 - d1*d1);
         return;
      }
   }

   /*   Find angle   */

   dgetcolumn(T1, X, ca, 4);
   dgetcolumn(T1, Y, cb, 4);
   dgetcolumn(T2, X, da, 4);
   dgetcolumn(T2, Y, db, 4);
   if( dvdot(axis, 3, cb) > 0.9) { /*  rotation axis is close to j - use  */
      dcross4( axis, ca, cc );      /* change in i axis to find angle  */
      dcross4( axis, da, cd );    
   } 
   else {                          /* use  */
      dcross4( axis, cb, cc );      /* change in j axis to find angle  */
      dcross4( axis, db, cd );    
   }
   d1 = dvmag( cc, 3 );
   d2 = dvmag( cd, 3 );
   /*  fprintf( stdout, "d1, d2:  %11.6lf %11.6lf \n", d1, d2 ); */
   if ( d1 == 0.0 && d2 == 0.0 ) {
      *cphi = 1.0;
      *sphi = 0.0;
   } 
   else {
      dvsmy( cc, 3, 1.0/d1, cc );
      dvsmy( cd, 3, 1.0/d2, cd );   /* normalize */
      *cphi = dvdot(cc, 3, cd);     /* cos(phi) given by dot of two vectors */
      dcross4( cc, cd, dd );
      *sphi = dvmag(dd, 3);
      if( dvdot(dd, 3, axis) < 0.0) {
         *sphi = -(*sphi);
      }
   }
   return;
}

/* ---------------------------------------------------------------------------- */
/* Functions to rotate vector or frame about an arbitrary axis */

void RotateVector(double *v, double *axis, double sphi, double cphi, double *y)
{
   /* v= sin(angle)(axis X v) + Cos(angle)v + (1-Cos(angle))(v . axis) * axis
         See Robots for shearing sheep pg 128 
      Note that v and y can be the same 
   */

   double db[6],dd[6];
   double scal;

   validate_dvector( db, 1, 4);
   validate_dvector( dd, 1, 4);
   scal = (1.0 - cphi) * dvdot(axis, 3, v);
   dvsmy( axis, 3, scal, dd );  
   dcross4( axis, v, db);
   dvpiv( db, 3, sphi, dd, dd );
   dvpiv( v, 3, cphi, dd, y ); 
   y[4] = 0.0;
}

void RotateFrame(double **T1, double *axis, double sphi, double cphi,
     double **T2)
{
   /*  Routine to rotate coord frame T1 about axis by angle phi to give T2
       using quaternion rotation.  Note axis, T1 and T2 are all defined in
       terms of one (world) coord frame - axis is NOT defined in terms of T1.
       It is assumed that T1, and hence T2, are orthogonalized.  T1 can be
       the same array as T2.
       sphi, cphi are sin and cosine of 'phi'.
       Peter Kovesi    May 1985, adapted to C James Trevelyan July 1994
   */
   /*   Temporary local data    */
   double ca[6],cb[6],cc[6],cd[6]; /*   workspace vectors   */
   validate_dvector( ca, 1, 4);
   validate_dvector( cb, 1, 4);
   validate_dvector( cc, 1, 4);
   validate_dvector( cd, 1, 4);

#ifdef V_CHECK
   if ( !valid_dframe( T1 ) )
      nrerror("Invalid 1st frame: RotateFrame\n");
   if ( !valid_dframe( T2 ) )
      nrerror("Invalid 2nd frame: RotateFrame\n");
   if ( !valid_d4vector_b( axis ) )
      nrerror("Invalid axis vector: RotateFrame\n");
#endif

   dgetcolumn( T1, X, ca, 4 );
   RotateVector(ca, axis, sphi, cphi, cb);
   dgetcolumn( T1, Y, ca, 4 );
   RotateVector(ca, axis, sphi, cphi, cc);
   dcross4( cb, cc, cd); /* T2's k axis */
   dputcolumn( cb, 4, T2, X );
   dputcolumn( cc, 4, T2, Y );
   dputcolumn( cd, 4, T2, Z );
   dgetcolumn( T1, U, ca, 4 );
   dputcolumn( ca, 4, T2, U );
}