/*

Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005 Matthew P. Hodges
This file is part of XMakemol.

XMakemol 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, or (at your option)
any later version.

XMakemol 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 XMakemol; see the file COPYING.  If not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <Xm/Xm.h>

#include "globals.h"
#include "bbox.h"
#include "draw.h"

/* Code to actually alter the atomic x,y,z values. This routine accepts
   first the axis to rotate about, second the angle, third a logical
   variable to state whether we want to store the geometry after the 
   rotation and fourth a logical variable to determine whether to honour
   the atoms[i].edit flags */

void
rotate_atoms(double *axis, double phi, Boolean store_g, Boolean rot_subset)
{
  void adjust_current_frame(void);
  void canvas_cb(Widget, XtPointer, XtPointer);
  void clear_message_area (void);
  void deactivate_region(void);
  void normalize_vec(double *);
  void rotate(double *);
  void rotate_vectors(double *, double);
  void set_rotate_axis(double *);
  void set_rotate_angle(double);
  void set_rotate_origin(double *);
  void store_coords(void);
  void update_bbox(void);
  void update_bond_matrix(Boolean);
  void update_lengths_dialog(Boolean);
  void vector_product(double *, double *, double *);

  int i,all_rotated;

  double local_com[3], local_mass;
  double atom_coords[3];

  all_rotated = 1;
  
  local_com[0] = local_com[1] = local_com[2] = 0;
  local_mass = 0;

  changing_frame = False;
  if((rotate_about == ROTATE_ABOUT_ORIGIN) || changing_frame)
    {
      set_rotate_origin(NULL);
      for(i = 0; i < no_atoms; i++)
        {
          if(atoms[i].edit == 0)
            {
              all_rotated = 0;
              atoms_sorted = 0; /* Depths have changed */
            }
        }
    }
  else if(rotate_about == ROTATE_ABOUT_LOCAL_COM)
    {
      for(i = 0; i < no_atoms; i++)
        {
          if(atoms[i].edit == 1)
            {
              local_com[0] += atoms[i].mass * atoms[i].x;
              local_com[1] += atoms[i].mass * atoms[i].y;
              local_com[2] += atoms[i].mass * atoms[i].z;
              
              local_mass += atoms[i].mass;
            }
          else
            {
              all_rotated = 0;
              atoms_sorted = 0; /* Depths have changed */
            }
        }
      
      for(i = 0; i < 3; i++)
        {
          if (local_mass == 0)
            {
              /* All dummy atoms? */
              local_com[i] = 0;
            }
          else
            {
              local_com[i] /= local_mass;
            }
        }
      set_rotate_origin(local_com);
    }
  
  set_rotate_axis(axis);
  set_rotate_angle(phi);

  for(i = 0; i < no_atoms; i++)
    {
      if((atoms[i].edit == 1) || (rot_subset == 0))
        {
          atom_coords[0] = atoms[i].x;
          atom_coords[1] = atoms[i].y;
          atom_coords[2] = atoms[i].z;

          rotate(atom_coords);

          atoms[i].x = atom_coords[0];
          atoms[i].y = atom_coords[1];
          atoms[i].z = atom_coords[2];
        }
    }

  if (bbox_available)
    {
      for (i = 0; i < 8; i++)
        {
          rotate (file_bbox.v[i]);
        }
    }

  if(store_g && all_rotated){ 

    /* modify global vectors describing current orientation of frame */

    set_rotate_origin(NULL);

    for(i=0;i<3;i++){
      rotate(global_matrix[i]);
    }
  }

  if(!all_rotated){
    update_bond_matrix(True);
  }

  rotate_vectors(axis, phi);

  update_bbox ();

  deactivate_region();

  clear_message_area ();

  canvas_cb(canvas,NULL,NULL);

  update_lengths_dialog(False);
}


void
rotate_vectors(double *axis, double phi)
{
  
  void rotate(double *);
  void set_rotate_axis(double *);
  void set_rotate_angle(double);
  void set_rotate_origin(double *);

  int i, j;

  set_rotate_axis(axis);
  set_rotate_angle(phi);
  set_rotate_origin(NULL);

  for (i = 0; i < no_atoms; i++)
    {
      for (j = 0; j < MAX_VECTORS_PER_ATOM; j++)
        {
          if ((atoms[i].has_vector > j) && (atoms[i].edit == 1))
            {
              rotate (atoms[i].v[j]);
            }
        }
    }
}
  

/* General purpose routines which rotate a vector relative to some
   origin about an axis by a certain angle */

/* The relative origin */

static Boolean ignore_rotate_origin = 0;
static double rotate_origin[3];

/* Local axis variables */

static double rotate_axis_l[3], rotate_axis_m[3], rotate_axis_n[3];

/* The angle by which the vector will be rotated */

static double rotate_angle, sina, cosa;

/* Public routines to set the above static variables */

void
set_rotate_origin(double *origin)
{

  if(origin != NULL)
    {
      ignore_rotate_origin = 0;
      rotate_origin[0] = origin[0];
      rotate_origin[1] = origin[1];
      rotate_origin[2] = origin[2];
    }
  else
    {
      ignore_rotate_origin = 1;
    }

}


void
set_rotate_axis(double *axis)
{

  void normalize_vec(double *);
  void vector_product(double *, double *, double *);

  int i;

  /* Set up local axis frame */

  /* rotate_axis_n is parallel to rotate_axis */

  for(i = 0; i < 3; i++)
    {
      rotate_axis_n[i] = axis[i];
    }

  normalize_vec(rotate_axis_n);

  /* rotate_axis_l is perpendicular to rotate_axis_n and lies in the
     001 plane */

  if(fabs(rotate_axis_n[2]) == 1.0)
    {
      rotate_axis_l[0] = 1.0;               /* Arbitrary */
      rotate_axis_l[1] = 0.0;
      rotate_axis_l[2] = 0.0;
    }
  else
    {
      rotate_axis_l[0] =  rotate_axis_n[1];
      rotate_axis_l[1] = -rotate_axis_n[0];
      rotate_axis_l[2] =  0.0;
    }

  normalize_vec(rotate_axis_l);

  /* m is perpendicular to l and n */

  vector_product(rotate_axis_m, rotate_axis_n, rotate_axis_l);


}


void
set_rotate_angle(double angle)
{

  rotate_angle = angle;

  sina = sin(rotate_angle);     /* sin of angle */
  cosa = cos(rotate_angle);     /* cos of angle */

}


/* The actually function to do the rotation -- pass pointer to a
   double foo[3] type variable */


void
rotate(double *v)
{

  double dot_product(double *, double *);

  int i;

  double a, b, c, ap, bp;


  /* Consider a rotation of vector v about an axis (l, m, n):

     v = a l + b m + c n   ---   ie vector in local axis frame

     rotate about n (local z) by angle p => rotation matrix given by:

         /  cos(phi) sin(phi)  0 \
     M = | -sin(phi) cos(phi)  0 |
         \     0        0      1 /

  */

  /* If rotate_origin is not NULL move the vector */

  if(ignore_rotate_origin == 0)
    {
      for(i = 0; i < 3; i++)
        {
          v[i] -= rotate_origin[i];
        }
    }

  /* decompose vector into components parallel to l, m and n which we
     label a, b and c */

  a = dot_product(v, rotate_axis_l);
  b = dot_product(v, rotate_axis_m);
  c = dot_product(v, rotate_axis_n);

  /* Now the rotation about n */
  
  ap = ((a * cosa) + (b * sina));
  bp = ((b * cosa) - (a * sina));

  /* Work out what new v is */

  a = ap; b = bp;

  for(i = 0; i < 3; i++)
    {
      v[i] = ((a * rotate_axis_l[i]) +
              (b * rotate_axis_m[i]) +
              (c * rotate_axis_n[i]));
    }

  /* If rotate_origin is not NULL move the vector */

  if(ignore_rotate_origin == 0)
    {
      for(i = 0; i < 3; i++)
        {
          v[i] += rotate_origin[i];
        }
    }
}


syntax highlighted by Code2HTML, v. 0.9.1