/*
    This file is part of the FElt finite element analysis package.
    Copyright (C) 1993 Jason I. Gobat and Darren C. Atkinson

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/***************************************************************************
*
* File:		draw3d.c
*
* Description:  Contains the routines to draw a FELT structure in three 
*		dimensions.   The 3d stuff is largely based on the
*		way that gnuplot does it.  This isn't the best way to
*		to do it in this case, but hey, c'est la vie. 
*		Some better determination of scaling and 
*		rotations would be nice ...
*
****************************************************************************/

# include <stdio.h>
# include <string.h>
# include <malloc.h>
# include <math.h>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Intrinsic.h>
# include <X11/StringDefs.h>
# include <X11/Shell.h>
# include <X11/Xaw/Form.h>
# include <X11/Xaw/AsciiText.h>
# include "Drawing.h"
# include "forms.h"
# include "dialog.h"
# include "globals.h"

double atof ();
double strtod ();

# define MaxNodesPerElement 50

# define XROT	40.0
# define YROT	-20.0
# define ZROT	0.0

static double x_min3d, y_min3d, z_min3d;
static double xscale3d, yscale3d, zscale3d;

static double transform [4][4];

static void Setup3D ( ),
	    Convert3Dto2D ( ),
	    MultiplyMatrices  ( ),
	    MatrixRotationX   ( ),
            MatrixRotationY   ( ),
            MatrixRotationZ   ( ),
            CreateScaleMatrix ( ),
            CreateUnitMatrix  ( );

void DrawStructure3D (data_file)
   char		*data_file;
{
   Arg		arglist [12];
   Cardinal	n;
   FILE 	*input;
   Point	points [MaxNodesPerElement];
   unsigned	count;
   int		i,j;
   char		buffer [80];
   char		*ptr1,
		*ptr2;
   Figure	dummy;
   float	maxX, minX, maxY, minY, maxZ, minZ, Xscale, Yscale;
   float	xdiff, ydiff;
   float	x,y,z,
		sx,sy;
   Dimension	width, height;
   XSizeHints	hints;
   float	w,h;
   int		num_pairs;
   struct pair {
      int npoints;
      float 	x[MaxNodesPerElement], 
  		y[MaxNodesPerElement], 
		z[MaxNodesPerElement];
   } *element;
   struct s_pair {
      float	x[MaxNodesPerElement], y[MaxNodesPerElement];
   } *s_element;

   input = fopen (data_file, "r");

   num_pairs = 100;

   element = (struct pair *) malloc (sizeof(struct pair) * num_pairs);
   if (element == NULL)
      Fatal ("allocation error creating 3d plot");

   s_element = (struct s_pair *) malloc (sizeof(struct s_pair) * num_pairs);
   if (s_element == NULL)
      Fatal ("allocation error creating 3d plot");

   count = 0;
   i = 0;
   while (fgets (buffer, 80, input) != NULL) {
      if (strcmp (buffer, "\n") != 0) {
         element [count].x[i] = strtod (buffer, &ptr1);
         ptr1 = strchr (buffer, ' ');
         element [count].y[i] = strtod (ptr1+1, &ptr2);
         ptr2 = strchr (ptr1+1, ' ');
         element [count].z[i] = atof (ptr2+1);
         i++;
      }
      else { 
         element [count].npoints = i;
         i = 0;
         count++;
         if (count >= num_pairs) {
            num_pairs += 200;
            element = (struct pair *) realloc (element, sizeof(struct pair)*num_pairs);
            if (element == NULL) 
               Fatal ("allocation error generating 3d plot"); 

            s_element = (struct s_pair *) realloc (s_element, sizeof(struct s_pair)*num_pairs);
            if (s_element == NULL) 
               Fatal ("allocation error generating 3d plot"); 
         }
      }
   }

   fclose (input);
   
   maxX = minX = element[0].x[0];
   maxY = minY = element[0].y[0];
   maxZ = minZ = element[0].z[0];

   for (i = 0 ; i < count ; i++) {
      for (j = 0 ; j < element [i].npoints ; j++) {

         x = element [i].x[j];
         y = element [i].y[j];
         z = element [i].z[j];

         if (x > maxX) maxX = x;
         else if (x < minX) minX = x;

         if (y > maxY) maxY = y;
         else if (y < minY) minY = y;

         if (z > maxZ) maxZ = z;
         else if (z < minZ) minY = z;
      }
   }

   Setup3D (minX,maxX,minY,maxY,minZ,maxZ);

   xdiff = maxX - minX;
   ydiff = maxY - minY;

   for (i = 0 ; i < count ; i++) {
      for (j = 0 ; j < element [i].npoints ; j++) {

         x = element [i].x[j];
         y = element [i].y[j];
         z = element [i].z[j];
         
         Convert3Dto2D (x, y, z, xdiff, ydiff, &sx, &sy);

         if (i == 0 && j == 0) {
            maxX = minX = sx;
            maxY = minY = sy;
         }
         else {
            if (sx > maxX) maxX = sx;
            else if (sx < minX) minX = sx;

            if (sy > maxY) maxY = sy;
            else if (sy < minY) minY = sy;
         } 
           
         s_element[i].x[j] = sx;
         s_element[i].y[j] = sy;
      }
   } 

   maxX += 0.05*(maxX - minX);
   maxY += 0.05*(maxY - minY);
   minY -= 0.05*(maxY - minY);
   minX -= 0.05*(maxX - minX);

   XtRealizeWidget (drawingShell);

   n = 0;
   XtSetArg (arglist [n], XtNwidth, &width); n++;
   XtSetArg (arglist [n], XtNheight, &height); n++;
   XtGetValues (drawing, arglist, n);

   w = (float) width;
   h = (float) height; 

   if ((maxX - minX)/w > (maxY - minY)/h) {
      Xscale = (float) (w/(maxX - minX));  
      Yscale = Xscale;
      height = (Dimension) ((maxY - minY)*Yscale);
   } else {
      Yscale = (float) (h/(maxY - minY));
      Xscale = Yscale;
      width = (Dimension) ((maxX - minX)*Xscale);
   }

   hints.width = width + 10;
   hints.height = height + 80;
   hints.flags = PSize;
 
   n = 0;
   XtSetArg (arglist [n], XtNxMin, Float2Arg(minX)); n++;
   XtSetArg (arglist [n], XtNxMax, Float2Arg(maxX)); n++;
   XtSetArg (arglist [n], XtNyMin, Float2Arg(minY)); n++;
   XtSetArg (arglist [n], XtNyMax, Float2Arg(maxY)); n++;
   XtSetArg (arglist [n], XtNxScale, Float2Arg(Xscale)); n++;
   XtSetArg (arglist [n], XtNyScale, Float2Arg(Yscale)); n++;
   XtSetArg (arglist [n], XtNwidth, width); n++;
   XtSetArg (arglist [n], XtNheight, height); n++;
   XtSetValues (drawing, arglist, n);
   
   n = 0;
   XtSetArg (arglist [n], XtNheight, (Dimension) hints.height); n++;
   XtSetArg (arglist [n], XtNwidth, (Dimension) hints.width); n++;
   XtSetValues (drawingShell, arglist, n);

   XSetNormalHints (XtDisplay (drawingShell), XtWindow (drawingShell), &hints);
 
   XtPopup (drawingShell, XtGrabNone);
   DW_RemoveAll (drawing);
   
   for (i = 0 ; i < count ; i++) {
      if (element [i].npoints > 2) {
         points [0].x = s_element [i].x[0];
         points [0].y = s_element [i].y[0];
         for (j = element [i].npoints - 1 ; j > 0 ; j--) {
            points [j].x = s_element [i].x[j] - s_element [i].x[j-1];
            points [j].y = s_element [i].y[j] - s_element [i].y[j-1];
         }
    
         dummy = DW_DrawPolygon (drawing, True, points, element [i].npoints);
      } else
         dummy = DW_DrawLine (drawing, s_element[i].x[0], s_element[i].y[0],
            s_element[i].x[1], s_element[i].y[1]); 
   }
   free (element);
   free (s_element);
} 

static void Convert3Dto2D (x, y, z, xdiff, ydiff, xt, yt)
   double 	x, y, z;
   float	xdiff, ydiff;
   float 	*xt, *yt;
{
   int 		i,j;
   double 	v[4], res[4],		     
	  	w = transform[3][3];

   v[0] = (x - x_min3d)*xscale3d - 1.0;
   v[1] = (y - y_min3d)*yscale3d - 1.0;
   v[2] = (z - z_min3d)*zscale3d - 1.0;
   v[3] = 1.0;

   for (i = 0; i < 2; i++) {	             
       res[i] = transform[3][i];     
       for (j = 0; j < 3; j++) 
          res[i] += v[j] * transform[j][i];
   }

   for (i = 0; i < 3; i++) 
      w += v[i] * transform[i][3];

   if (w == 0) 
      w = 0.00001;

   *xt = ((res[0] * 10.0 / w));
   *yt = ((res[1] * 10.0*ydiff/xdiff / w));
}

static void Setup3D (min_x, max_x, min_y, max_y, min_z, max_z)
   double 	min_x, max_x;
   double 	min_y, max_y;
   double 	min_z, max_z;
{
   double 	ztemp, temp;
   double	zsc;
   double 	mat [4][4];

	/*
	 * perform the rotations 
	 */

   MatrixRotationY (YROT, transform);
   MatrixRotationX (XROT, mat);
   MultiplyMatrices (transform, transform, mat);
   MatrixRotationZ (ZROT, mat);
   MultiplyMatrices (transform, transform, mat);
   CreateScaleMatrix (0.5, 0.5, 0.5, mat);
   MultiplyMatrices (transform, transform, mat);

	/*
	 * fudge min / max so the z direction will scale
	 */

   zsc = 0.2;		/* ideally there would be a way to set this! */

   ztemp = (max_z - min_z) / (2.0 * zsc);
   temp = (max_z + min_z) / 2.0;
   min_z = temp - ztemp;
   max_z = temp + ztemp;

	/* 
	 * set the global variables 
	 */

   x_min3d = min_x;
   y_min3d = min_y;
   z_min3d = min_z;

   xscale3d = 2.0/(max_x - min_x);
   yscale3d = 2.0/(max_y - min_y);
   if (max_z - min_z > 0)
      zscale3d = 2.0/(max_z - min_z);
   else
      zscale3d = 0.0;
}

static void CreateUnitMatrix (mat)
   double 	mat[4][4];
{
   unsigned	i, j;

   for (i = 0; i < 4; i++) {
      for (j = 0; j < 4; j++) {

         if (i == j)
            mat[i][j] = 1.0;
         else
            mat[i][j] = 0.0;
      }
   }
}

static void CreateScaleMatrix (sx, sy, sz, mat)
   double 	sx, sy, sz;
   double 	mat[4][4];
{
    CreateUnitMatrix(mat);                              

    mat[0][0] = sx;
    mat[1][1] = sy;
    mat[2][2] = sz;
}

static void MatrixRotationX (theta, mat)
   double 	theta;
   double 	mat[4][4];
{
   theta *= M_PI / 180.0;

   CreateUnitMatrix(mat);                              
   mat[1][1] = cos(theta);
   mat[1][2] = -sin(theta);
   mat[2][1] = sin(theta);
   mat[2][2] = cos(theta);
}

static void MatrixRotationY (theta, mat)
   double 	theta;
   double 	mat[4][4];
{
   theta *= M_PI / 180.0;

   CreateUnitMatrix(mat);                             
   mat[0][0] = cos(theta);
   mat[0][2] = -sin(theta);
   mat[2][0] = sin(theta);
   mat[2][2] = cos(theta);
}

static void MatrixRotationZ (theta, mat)
   double 	theta;
   double 	mat[4][4];
{
   theta *= M_PI / 180.0;

   CreateUnitMatrix(mat);                            
   mat[0][0] = cos(theta);
   mat[0][1] = -sin(theta);
   mat[1][0] = sin(theta);
   mat[1][1] = cos(theta);
}

static void MultiplyMatrices (result, mat_a, mat_b)
   double 	result [4][4], 
           	mat_a [4][4], 
           	mat_b [4][4];
{
   unsigned	i,j,k;
   double 	temp[4][4];

   for (i = 0; i < 4; i++) {
      for (j = 0; j < 4; j++) {
         temp[i][j] = 0;
         for (k = 0; k < 4; k++) 
            temp[i][j] += mat_a[i][k] * mat_b[k][j];
      }
   }

   for (i = 0; i < 4; i++) 
      for (j = 0; j < 4; j++) 
         result[i][j] = temp[i][j];
}
