/*
======================================================================
libtx.c

Alexander Enzmann
Ernie Wright  01 Jun 05

A library of transformation tracking routines.  Modified for use in a
LightWave Modeler plug-in.  Routines that print have been removed.
====================================================================== */

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


typedef struct tx_struct *tx_ptr;
typedef struct tx_struct {
   MATRIX tx;
   tx_ptr next;
   };


MATRIX IdentityTx =
   {{1, 0, 0, 0},
    {0, 1, 0, 0},
    {0, 0, 1, 0},
    {0, 0, 0, 1}};

static MATRIX CurrentTx =
   {{1, 0, 0, 0},
    {0, 1, 0, 0},
    {0, 0, 1, 0},
    {0, 0, 0, 1}};

static tx_ptr TxStack = NULL;


/* Return 1 if there is an active transformation, 0 if not */
int lib_tx_active( void )
{
   int i, j;
   for (i=0;i<4;i++)
      for (j=0;j<4;j++)
         if (fabs(CurrentTx[i][j] - (i == j ? 1.0 : 0.0)) > EPSILON)
            return 1;
         return 0;
}

/* Copy the current transform into mat */
void lib_get_current_tx(MATRIX mat)
{
   lib_copy_matrix(mat, CurrentTx);
}

/* Copy matrix mat into the current transform */
void lib_set_current_tx(MATRIX mat)
{
   lib_copy_matrix(CurrentTx, mat);
}


void
lib_tx_pop( void )
{
   tx_ptr last_tx;

   if (TxStack != NULL) {
      last_tx = TxStack;
      lib_copy_matrix(CurrentTx, last_tx->tx);
      TxStack = TxStack->next;
      free(last_tx);
   }
}

void
lib_tx_push( void )
{
   tx_ptr new_tx;

   if (new_tx = malloc(sizeof(struct tx_struct))) {
      lib_copy_matrix(new_tx->tx, CurrentTx);
      new_tx->next = TxStack;
      TxStack = new_tx;
   }
}

void lib_tx_rotate(int axis, double angle)
{
    MATRIX mx1, mx2;

    lib_create_rotate_matrix(mx1, axis, angle);
    lib_copy_matrix(mx2, CurrentTx);
    lib_matrix_multiply(CurrentTx, mx1, mx2);
}

void lib_tx_scale(COORD3 vec)
{
    MATRIX mx1, mx2;

    lib_create_scale_matrix(mx1, vec);
    lib_copy_matrix(mx2, CurrentTx);
    lib_matrix_multiply(CurrentTx, mx1, mx2);
}

void lib_tx_translate(COORD3 vec)
{
    MATRIX mx1, mx2;

    lib_create_translate_matrix(mx1, vec);
    lib_copy_matrix(mx2, CurrentTx);
    lib_matrix_multiply(CurrentTx, mx1, mx2);
}

/*-----------------------------------------------------------------*/
/* From Graphics Gems II, "unmatrix" written by Spencer W. Thomas.
Note that tran has to have at least 16 entries which will be set
to:
Sx, Sy, Sz, Shearxy, Shearxz, Shearyz, Rx, Ry, Rz, Tx, Ty, Tz,
P(x, y, z, w)
*/

int lib_tx_unwind(MATRIX tx_mat, double *tran)
{
   int i, j;
   MATRIX locmat, pmat, invpmat;
   COORD4 prhs, psol;
   COORD3 row[3];

   lib_copy_matrix(locmat, tx_mat);

   /* Divide through by the homogenous value (normalize) */
   if (locmat[3][3] != 0.0)
      for (i=0;i<4;i++)
         for (j=0;j<4;j++)
            locmat[i][j] /= locmat[3][3];

   /* pmat is used to solve for perspective, but it also provides
      an easy way to test for singularity of the upper 3x3 component */
   lib_copy_matrix(pmat, locmat);
   for (i=0;i<3;i++)
      pmat[i][3] = 0.0;
   pmat[3][3] = 1.0;

   if (lib_matrix_det4x4(pmat) == 0.0)
      return 0;

   /* First, isolate perspective */
   if (locmat[0][3] != 0.0 ||
      locmat[1][3] != 0.0 ||
      locmat[2][3] != 0.0) {
      /* prhs is the right hand side of the equation */
      prhs[X] = locmat[0][3];
      prhs[Y] = locmat[1][3];
      prhs[Z] = locmat[2][3];
      prhs[W] = locmat[3][3];

      /* Solve the equation by inverting pmat and multiplying
      prhs by the inverse.  */
      lib_invert_matrix(invpmat, pmat);
      lib_transform_coord(psol, prhs, invpmat);

      /* Save the perspective information */
      tran[U_PERSPX] = psol[X];
      tran[U_PERSPY] = psol[Y];
      tran[U_PERSPZ] = psol[Z];
      tran[U_PERSPW] = psol[W];

      /* Clear the perspective partition */
      locmat[0][3] = 0.0;
      locmat[1][3] = 0.0;
      locmat[2][3] = 0.0;
      locmat[3][3] = 1.0;
   }
   else {
      /* No perspective */
      tran[U_PERSPX] = 0.0;
      tran[U_PERSPY] = 0.0;
      tran[U_PERSPZ] = 0.0;
      tran[U_PERSPW] = 0.0;
   }

   /* Pull out the translation */
   for (i=0;i<3;i++) {
      tran[U_TRANSX+i] = locmat[3][i];
      locmat[3][i] = 0.0;
   }

   /* Figure out scale and shear */
   for (i=0;i<3;i++) {
      row[i][X] = locmat[i][0];
      row[i][Y] = locmat[i][1];
      row[i][Z] = locmat[i][2];
   }
   /* Compute X scale factor and normalize the first row */
   tran[U_SCALEX] = lib_normalize_vector(row[0]);

   /* Compute XY shear factor and make 2nd row orthogonal to 1st */
   tran[U_SHEARXY] = DOT_PRODUCT(row[0], row[1]);
   COMB_COORD(row[1], row[1], row[0], 1.0, -tran[U_SHEARXY]);

   /* Compute Y scale and normalize 2nd row */
   tran[U_SCALEY] = lib_normalize_vector(row[1]);
   tran[U_SHEARXY] /= tran[U_SCALEY];

   /* Compute XZ and YZ shears, orthogonalize 3rd row */
   tran[U_SHEARXZ] = DOT_PRODUCT(row[0], row[2]);
   COMB_COORD(row[2], row[2], row[0], 1.0, -tran[U_SHEARXZ]);
   tran[U_SHEARYZ] = DOT_PRODUCT(row[1], row[2]);
   COMB_COORD(row[2], row[2], row[1], 1.0, -tran[U_SHEARYZ]);

   /* Get Z scale and normalize 3rd row */
   tran[U_SCALEZ] = lib_normalize_vector(row[2]);
   tran[U_SHEARXZ] /= tran[U_SCALEZ];
   tran[U_SHEARYZ] /= tran[U_SCALEZ];

   /* At this point, the matrix (in rows[]) is orthonormal.
      Check for a coordinate system flip.  If the determinant
      is -1, then negate thematrix and the scaling factors */
   CROSS(prhs, row[1], row[2]);
   if (DOT_PRODUCT(row[0], prhs) < 0.0) {
      for (i=0;i<3;i++) {
         tran[U_SCALEX+i] *= -1.0;
         row[i][X] *= -1.0;
         row[i][Y] *= -1.0;
         row[i][Z] *= -1.0;
      }
   }

   /* Get the rotations out */
   tran[U_ROTATEY] = asin(-row[0][Z]);
   if (cos(tran[U_ROTATEY]) != 0) {
      if (fabs(row[1][Z]) < EPSILON && fabs(row[2][Z]) < EPSILON)
         tran[U_ROTATEX] = 0.0;
      else
         tran[U_ROTATEX] = atan2(row[1][Z], row[2][Z]);
      if (fabs(row[0][Y]) < EPSILON && fabs(row[0][X]) < EPSILON)
         tran[U_ROTATEZ] = 0.0;
      else
         tran[U_ROTATEZ] = atan2(row[0][Y], row[0][X]);
   }
   else {
      tran[U_ROTATEX] = atan2(row[1][X], row[1][Y]);
      tran[U_ROTATEZ] = 0.0;
   }

   return 1;
}
