/* Copyright amitabh varshney@cs.unc.edu. All Rights Reserved. March 16, 1994 */
/*--------------------------------------------------------------------------------
tessel_cases.c

This file has routines to identify what kind of moelcular surface case is to be
used and accordingly calls the routines in tessel_patches.c. 

The main consideration in writing this has been that the three kinds of patches
-- concave spherical triangular, convex spherical, and toroidal -- all meet each
other at the same points.

Here's a brief description of the four cases I-IV, that are used for tessllation 
of the final molecular surface into a mesh. Here a "feasible face" means a face
of the feasible cell. For description of a feasible cell, one can refer to any
of our publications mentioned in the README file.

Case I  : One isolated vertex of a feasible face is outside the extended-radius 
~~~~~~	  sphere for atom i. In this case the patches generated by atom i are: 
	  (a) half a toroidal patch
	  (b) two one-third concave triangular patches, one on either side 
	      of the torus.
          (c) one convex spherical patch.
Case II : More than one connected vertices of the feasible face are outside the 
~~~~~~~	  extended-radius sphere for atom i. In this case the patches generated by 
	  atom i are: 
	  (a) half a toroidal patch
	  (b) two one-third concave triangular patches, one on either side 
	      of the torus.
          (c) one convex spherical patch.
Note: Except for the fact that an extra traversal is needed to identify all the
connected vertices that are outside the extended-radius sphere, cases I and II
are the same. Hence, I have merged the two and have same routines for dealing
with the two. In that case, why name them differently? For historical reasons.
Initially these two cases had started out being dealt with differently. The 
whole structure of this program was quite different then and it made sense
to treat them individually. Not any more though. Perhaps in future versions, 
I will just combine the two into one case.

Case III: In this case, all vertices of the feasible face lie outside the 
~~~~~~~~  extended-radius sphere for the atom i, but the face itself intersects
	  the sphere. This gives rise to 
	  (a) half of a toroidal patch that is "complete" i.e. makes a full circle.
	  (b) one convex spherical patch
	  per atom.

Case IV : In this case, all vertices of the feasible face lie outside the 
~~~~~~~   extended-radius sphere for the atom i, and the face itself does not
	  intersect the sphere. This gives rise to a single convex spherical 
	  patch. 
----------------------------------------------------------------------------------*/

#include "surf.h"
#include "dual.h"

/*--------------------------------------------------------------------------------
gen_case_I_II takes care of cases I and II. 
----------------------------------------------------------------------------------*/
/* generate patches corresponding to cases I_II */
gen_case_I_II(temp_torus, cons)
struct Torus	 *temp_torus;
Vector		 *cons;
{ 
  int		 i, k, n, vert1_id, vert2_id; 
  POINT		 *comp_verts;
  VertexType	 con_center[2];
  POINT		 cir_center; /* center of the circle of intersection */
  double	 cir_radius; /* radius of the circle of intersection */
  struct redge   *edge[2];
  struct rvertex *vert[2];
  int		 end_atoms[2], face_atoms[2];
  int		 ipt[2]; /* stores which is the intersection pt: 0 or 1 */
  Vector	 half_space;
  MatrixD4	 inv_transf;
  double	 angle, find_torus_angle();
  int		 atom_id, num_int_verts;
  int		 torus_cusp, acute;

  n = rfaces[temp_torus->face_id].num_verts;
  num_int_verts = temp_torus->num_int_verts;
  vert1_id = (temp_torus->edge_id[0] == n-1)? 0: temp_torus->edge_id[0]+1;
  vert2_id = temp_torus->edge_id[1];

  vert[0] = rfaces[temp_torus->face_id].vert[vert1_id];
  vert[1] = rfaces[temp_torus->face_id].vert[vert2_id];
  atom_id = temp_torus->face_atoms[0];

  for(i = 0; i < 2; i++)
  { face_atoms[i] = temp_torus->face_atoms[i];
    end_atoms[i] = temp_torus->end_atoms[i];
    edge[i] = rfaces[temp_torus->face_id].edge[temp_torus->edge_id[i]];

    ipt[i] = ((edge[i]->int_pts[1][0] != HUGE_VAL)&&
	      (edge[i]->end_pts[1]==vert[i]))? 1: 0;
     
  }
  /* allocate space for component vertices: (1 + num_int_verts + 1) */
  ALLOCN(comp_verts, POINT, num_int_verts+2);

  /* convert from local coord system of atoms[atom_id] to global coords */
  VEC3_V_OP_V(comp_verts[0], edge[0]->int_pts[ipt[0]],+, atoms[atom_id].center);
  for(i = 1, k = vert1_id-1; i <= num_int_verts; i++)
  { k = (k == n-1)? 0: k+1;
    VEC3_V_OP_V(comp_verts[i],rfaces[temp_torus->face_id].vert[k]->v, +,atoms[atom_id].center);
  }
  VEC3_V_OP_V(comp_verts[num_int_verts+1], edge[1]->int_pts[ipt[1]],+, 
	      atoms[atom_id].center);

  VEC_ASN_OP(half_space.coeff, =, cons[rfaces[temp_torus->face_id].id].coeff, 4);

  torus_cusp = find_circle(face_atoms, half_space, cir_center, &cir_radius);

  if (cir_radius > 0)
  { /* find the angle between the first and last comp_verts wrt cir_center */
    angle = find_torus_angle(comp_verts, num_int_verts+2, cir_center);
    if (FP_EQ_EPS(angle, PI, GP_EPS)) 
    { i = (num_int_verts+2)/2;
      compute_transf_mat_I_II(comp_verts[0], comp_verts[i], 
			  inv_transf, cir_center, cir_radius, TRUE);

    }
    else 
    { acute = (angle < PI);
      compute_transf_mat_I_II(comp_verts[0], comp_verts[num_int_verts+1], 
			  inv_transf, cir_center, cir_radius, acute);
    }
    /* convert the half_space equation from the New_Origin-Coord system
       to the global coord system
    */
    half_space.coeff[3] += DOTPROD3(half_space.coeff, New_Origin.coeff);

    find_concave_center(face_atoms,end_atoms[0],comp_verts[0],&con_center[0]);

    find_concave_center(face_atoms,end_atoms[1],comp_verts[num_int_verts+1],&con_center[1]);

    /* all quants are passed to gen_patches_I in global coord system */
    gen_patches_I_II(face_atoms, comp_verts, num_int_verts+2, con_center, cir_radius, 
		     inv_transf, angle, temp_torus, torus_cusp);
  }

  free(comp_verts);
}


/*--------------------------------------------------------------------------------
gen_case_III_IV determines if the case is III or IV and accordingly calls 
gen_case_III or gen_case_IV
----------------------------------------------------------------------------------*/
gen_case_III_IV(atom_id, face_id, cons)
int		atom_id, face_id;
Vector		*cons;
{ 
  int		i, j, case_III;
  int		face_atoms[2];
  POINT		center;
  double	cir_radius;
  struct rface  *face_ptr;
  POINT		major_normal, tmp_normal;
  POINT		diff[2];

  face_atoms[0] = atom_id;
  face_atoms[1] = cons[rfaces[face_id].id].atom_id;

  if (face_atoms[1] >= 0)
  { /* find the circle of intersection of this plane with the sphere */
    find_circle(face_atoms, cons[rfaces[face_id].id], center, &cir_radius);

    if (cir_radius > 0)
    { /* check to see if this face contains the center of the 
       circle of intersection */
      face_ptr = &rfaces[face_id];
      VEC3_V_OP_V_OP_V(diff[0], face_ptr->vert[0]->v, -, center, +, atoms[atom_id].center );
      NORMALIZE3(diff[0]);
      VEC3_V_OP_V_OP_V(diff[1], face_ptr->vert[1]->v, -, center, +, atoms[atom_id].center);
      NORMALIZE3(diff[1]);
      CROSSPROD3(major_normal, diff[0], diff[1]);

      for(i = 1, case_III = TRUE; i < face_ptr->num_verts; i++)
      { j = (i+1)%face_ptr->num_verts;
        VEC3_V_OP_V_OP_V(diff[0], face_ptr->vert[i]->v, -, center, +, atoms[atom_id].center );
        NORMALIZE3(diff[0]);
        VEC3_V_OP_V_OP_V(diff[1], face_ptr->vert[j]->v, -, center, +, atoms[atom_id].center );
        NORMALIZE3(diff[1]);
        CROSSPROD3(tmp_normal, diff[0], diff[1]);
        if (DOTPROD3(tmp_normal, major_normal) < 0)
        { case_III = FALSE;
          break;
        }
      }
      if (case_III)
        gen_case_III(atom_id, face_id, cons); 
      else /* this face doesn't intersect circle so generate a type IV patch */
        gen_case_IV(atom_id, face_id, cons);
    }
    else /* circle is so small that we can consider this a case IV */
     gen_case_IV(atom_id, face_id, cons);
  } 
  else  
     /* Case IV: this is one of the extra extent-planes, so it doesn't intersect 
        the extended-radius sphere at all, but its vertices are valid */
    gen_case_IV(atom_id, face_id, cons);
}

/*--------------------------------------------------------------------------------
gen_case_III handles the case of one complete torus.
----------------------------------------------------------------------------------*/
gen_case_III(atom_id, face_id, cons)
int		face_id, atom_id;
Vector		*cons;
{ 
  int		 i, n;
  POINT		 *comp_verts;
  POINT		 cir_center; /* center of the circle of intersection */
  double	 cir_radius; /* radius of the circle of intersection */
  int		 face_atoms[2];
  Vector	 half_space;
  MatrixD4	 inv_transf;
  int		 torus_cusp;

  n = rfaces[face_id].num_verts;
  face_atoms[0] = atom_id;
  face_atoms[1] = cons[rfaces[face_id].id].atom_id;

  /* allocate space for component vertices */
  ALLOCN(comp_verts, POINT, n+1);

  /* convert from local coord system of atoms[atom_id] to global coords */
  for(i = 0; i < n; i++)
  VEC3_V_OP_V(comp_verts[i],rfaces[face_id].vert[i]->v,+,atoms[atom_id].center);
  VEC3_ASN_OP(comp_verts[n], =, comp_verts[0]);
 
  VEC_ASN_OP(half_space.coeff, =, cons[rfaces[face_id].id].coeff, 4);

  torus_cusp = find_circle(face_atoms, half_space, cir_center, &cir_radius);
  compute_transf_mat_III(half_space, inv_transf, cir_center, cir_radius);

  /* convert the half_space equation from the New_Origin-Coord system
     to the global coord system */
  half_space.coeff[3] += DOTPROD3(half_space.coeff, New_Origin.coeff);

  /* all quants are passed to gen_patches_III in global coord system */
  gen_patches_III(face_atoms, comp_verts,n+1,cir_center, cir_radius,
		  inv_transf, torus_cusp);

  free(comp_verts);
}


/*--------------------------------------------------------------------------------
gen_case_IV handles the case IV
----------------------------------------------------------------------------------*/
gen_case_IV(atom_id, face_id, cons)
int		atom_id, face_id;
Vector		*cons;
{
  int		 i, n;
  POINT		 *comp_verts;
  Vector	 half_space;

  n = rfaces[face_id].num_verts;

  /* allocate space for component vertices */
  ALLOCN(comp_verts, POINT, n);

  /* convert from local coord system of atoms[atom_id] to global coords */
  for(i = 0; i < n; i++)
   VEC3_V_OP_V(comp_verts[i],rfaces[face_id].vert[i]->v,+,atoms[atom_id].center);

  /* convert the half_space equation from the New_Origin-Coord system
     to the global coord system
  */
  VEC_ASN_OP(half_space.coeff, = , cons[rfaces[face_id].id].coeff, 4);
  half_space.coeff[3] += DOTPROD3(half_space.coeff, New_Origin.coeff);

  /* all quants are passed to gen_patches_IV in global coord system */
  gen_patches_IV(atom_id, comp_verts, n);

  free(comp_verts);
}

/*--------------------------------------------------------------------------------
compute_transf_mat_I_II finds the transformation matrix that makes the plane of 
vec0, pt0, pt1 align with _one_ of the principal planes and translates the origin 
to be the center of the circle of intersection of the two atoms which formed this 
hyperplane.  This is used in gen_cases_I_II
----------------------------------------------------------------------------------*/
compute_transf_mat_I_II(pt0, pt1, inv_transf,center,radius, acute)
POINT		pt0, pt1;
MatrixD4        inv_transf;
POINT		center;
double          radius;
int		acute;
{
  POINT         vec0, vec1, vec2, vec3;

  VEC3_V_OP_V(vec0, pt0, -, center);
  VEC3_V_OP_V(vec1, pt1, -, center);
  NORMALIZE3(vec0); NORMALIZE3(vec1);
  ZERO3(vec0); ZERO3(vec1);

  IDENTMAT(inv_transf, 4);
  /* make vec0 rotate to x-axis */
  SETCOL(X, inv_transf, vec0, 3);
  /* make (vec0 x vec1) rotate to z-axis */
  CROSSPROD3(vec2, vec0, vec1);
  NORMALIZE3(vec2);
  if (!acute) /* make (vec1 x vec0) rotate to z-axis */
    VEC3_NEG(vec2, vec2);
  SETCOL(Z, inv_transf, vec2, 3);
  /* make (vec2 x vec0) rotate to y-axis */
  CROSSPROD3(vec3, vec2, vec0);
  NORMALIZE3(vec3);
  SETCOL(Y, inv_transf, vec3, 3);

  /* translate the origin to be the center of the circle of intersection */
  SETCOL(3, inv_transf, center, 3);

  /* multiply columns 0 and 1 of inv_transf with radius */
  GETCOL(X, inv_transf, vec0, 3);
  VEC3_V_OP_S(vec0, vec0, *,radius);
  SETCOL(X, inv_transf, vec0, 3);
  GETCOL(Y, inv_transf, vec0, 3);
  VEC3_V_OP_S(vec0, vec0, *,radius);
  SETCOL(Y, inv_transf, vec0, 3);
}

/*--------------------------------------------------------------------------------
compute_transf_mat_I_II finds the transformation that makes the hyperplane 
constraint align with _one_ of the principal planes and translates the origin 
to be the center of the circle of intersection of the two atoms which formed 
this hyperplane.  This is used in gen_cases_III
----------------------------------------------------------------------------------*/
compute_transf_mat_III(constraint, inv_transf, center, radius)
Vector          constraint;
MatrixD4        inv_transf;
POINT		center;
double          radius;
{
  int           j, n;
  int           done;
  POINT         tempv1, tempv2;
  POINT		temp_cons;

  VEC3_ASN_OP(temp_cons, =, constraint.coeff);
  ZERO3(temp_cons);
  for(j = 0; j < 3; j++)
  { if (temp_cons[j] != 0) 
    { if (temp_cons[j] < 0) VEC3_NEG(temp_cons, temp_cons);
      break;
    }
  }

  IDENTMAT(inv_transf, 4);
  SETCOL(Z, inv_transf, temp_cons, 3);
  /* find a vector that is normal to the plane-normal ie it lies on the plane */
  for(j = n = 0; j < 3; j++)
  { if (temp_cons[j] != 0)
    { tempv1[j] = ONE/temp_cons[j];
      n++;
    }
    else
     tempv1[j] = ONE;
  }
  done = FALSE;
  for(j = 0; ((j < 3) && (!done)); j++)
  if (temp_cons[j] != 0)
  { switch(n)
    { case 1:
        tempv1[j] = ZERO;
        break;
      case 2:
        tempv1[j] = -tempv1[j];
        break;
      case 3:
        tempv1[0] = -2*tempv1[0];
        break;
    }
    done = TRUE;
  }

  NORMALIZE3(tempv1);
  SETCOL(X,inv_transf,tempv1,3);

  /* obtain the third vector that is to rotate to the y-axis*/
  CROSSPROD3(tempv2, temp_cons, tempv1);
  NORMALIZE3(tempv2);
  SETCOL(Y, inv_transf, tempv2, 3);

  /* translate the origin to be the center of the circle of intersection */
  SETCOL(3, inv_transf, center, 3);

  /* multiply columns 0 and 1 of inv_transf with radius */
  GETCOL(X, inv_transf, tempv1, 3);
  VEC3_V_OP_S(tempv1, tempv1, *,radius);
  SETCOL(X, inv_transf, tempv1, 3);
  GETCOL(Y, inv_transf, tempv1, 3);
  VEC3_V_OP_S(tempv1, tempv1, *,radius);
  SETCOL(Y, inv_transf, tempv1, 3);
}

/*--------------------------------------------------------------------------------
find_torus_angle finds the angle subtended by the torus, given that the center of 
the circle (from the intersection of this face with the extended-radius sphere) 
is center, and the pts[0..num_pts-1] are the vertices of the face that are outside 
the circle and all form the exterior of the torus. 
----------------------------------------------------------------------------------*/
double
find_torus_angle(pts, num_pts, center)
POINT		*pts, center;
int		num_pts;
{ 
  POINT		vec0, vecn;
  double	theta;
  int		i, acute;
  Vector	dividing_plane;

  VEC3_V_OP_V(vec0, pts[0], -, center);
  NORMALIZE3(vec0); ZERO3(vec0); 
  VEC3_V_OP_V(vecn, pts[num_pts-1], -, center);
  NORMALIZE3(vecn); ZERO3(vecn); 
  theta = DOTPROD3(vec0, vecn);
  if ((theta >= 1.0) && (theta <= 1.0 + GP_EPS)) theta = 0;
  else if ((theta <= -1.0) && (theta >= -1.0-GP_EPS)) theta = PI;
  else theta = acos(theta);

  /* first check to see whether one needs to consider the acute or the obtuse 
     side of the angle between vec0 and vecn. i do this by forming a plane 
     that is perp to (vec0+vecn, and passes through 'center'. Then I check 
     to see if all the other pts lie on the same side of this plane as pts[0], 
     or not. If they all lie on the same side, then the angle of the torus is 
     acute, otherwise it is obtuse.
  */
  /* eq of dividing plane is ax + by + cz = d */
  VEC3_V_OP_V(dividing_plane.coeff, vec0, +, vecn);
  NORMALIZE3(dividing_plane.coeff);
  dividing_plane.coeff[3] = DOTPROD3(dividing_plane.coeff, center);
  /* make sure that pts[0] lies on the side such that ax + by + cz > d */
  if (DOTPROD3(dividing_plane.coeff, pts[0]) < dividing_plane.coeff[3])
    VEC_NEG(dividing_plane.coeff, dividing_plane.coeff, 4);

  for(i = 1, acute = TRUE; i < num_pts; i++)
  { if (DOTPROD3(dividing_plane.coeff, pts[i]) < dividing_plane.coeff[3])
    { acute = FALSE;
      break;
    }
  }
  if (!acute) theta = TWO_PI - theta;
  return(theta);
}


/*--------------------------------------------------------------------------------
find_circle returns the center and radius of the circle of intersection on the 
common plane between two atoms in global coord system. 
This assumes 
   (a) the eq of plane is [0]x + [1]y + [2]z = [3] and
   (b) the plane is in the New_Origin coord system
   
This returns TRUE if a 2-atom torus cusp exists, else returns FALSE
----------------------------------------------------------------------------------*/
int
find_circle(face_atoms, plane, center, radius)
int		face_atoms[2];
Vector		plane;
POINT		center;
double		*radius;
{ 
  Vector	global_plane;
  double	t, r;
  POINT		C1_C0;
  
  /* convert plane to global coordinate system */ 
  VEC_ASN_OP(global_plane.coeff, =, plane.coeff, 4);
  global_plane.coeff[3] += DOTPROD3(global_plane.coeff, New_Origin.coeff);

  /* substitute the equation C0 + t(C1 - C0), in global_plane and find the
     value of t - this is the center of the circle
  */
  VEC3_V_OP_V(C1_C0, atoms[face_atoms[1]].center,-,atoms[face_atoms[0]].center);
  t = global_plane.coeff[3] - 
      DOTPROD3(global_plane.coeff, atoms[face_atoms[0]].center);
  t /= (DOTPROD3(global_plane.coeff, C1_C0));
  VEC3_V_OP_V_OP_S(center, atoms[face_atoms[0]].center, +, C1_C0, *, t);
  
  /* find the radius of the circle */
  r = Probe_radius + atoms[face_atoms[0]].radius;
  *radius = r*r - t*t*DOTPROD3(C1_C0, C1_C0);

  if (fabs(*radius) < GP_EPS)
    *radius = 0;

  if (*radius < 0)
  { printf("find_circle(): Negative circle radius: atoms (%d %d) radius %lf\n", 
	    face_atoms[0], face_atoms[1], *radius);
    *radius = 0;
  }
  *radius = sqrt(*radius);

  /* decide if a torus cusp will exist or not */
  if (*radius < Probe_radius)
  { t = DOTPROD3(atoms[face_atoms[0]].center, global_plane.coeff) 
	- global_plane.coeff[3];
    r = DOTPROD3(atoms[face_atoms[1]].center, global_plane.coeff) 
	- global_plane.coeff[3];
    if (r*t >= 0) /* both atoms are on the same side of plane so no cusp */
      return(FALSE);
    else
      return(TRUE);
  }
  else 
    return(FALSE);
}


/*--------------------------------------------------------------------------------
find_concave_center finds the center of a concave patch. This is used to divide
the concave patch into three concave triangular patches -- one each is computed
by the three respective atoms -- therefore in future if each atom is colored 
differently, the concave patch would be colored by three different colors.
This assumes probe_pos is in global coord system.
----------------------------------------------------------------------------------*/
find_concave_center(face_atoms, k, probe_pos, concave_center)
int		face_atoms[2];
int		k;
POINT		probe_pos;
VertexType	*concave_center;
{ 
  POINT		diff;
  double	dtemp;
  int		i, j, l;

  i = face_atoms[0]; j = face_atoms[1];

  /* take the average of the three vectors from the probe_pos
     to the centers of the three atoms i, j, and k
  */
  for(l = dtemp = 0; l < 3; l++)
  { diff[l] = atoms[i].center[l] + atoms[j].center[l] + atoms[k].center[l]
	      - 3*probe_pos[l];
    dtemp   += diff[l]*diff[l];
  }
 
  /* normalize diff, multiply by Probe_radius, and add to 
     probe_pos to get the concave center 
  */
  dtemp = -ONE/sqrt(dtemp);
  VEC3_V_OP_S(concave_center->Normal, diff, *, dtemp);
  VEC3_V_OP_V_OP_S(concave_center->Coord, probe_pos, -, concave_center->Normal, *, Probe_radius);
}
