/*
 * list6.c - utilities that use linked-list data-structures 
 *
 * Find the triangles in different regions that share common points
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "CNplot.h"

void   CNgenerate_boundary_from_points();
static void generate_region_colors();
void   CNgenerate_ptseg_list();
static void       add_point();
static void       add_ptseg();
static int        shared_triangles();
static int        shared_tria_is_flagged();
void   CNfind_boundary_ptsegs();
void   CNfind_boundary_polys();
static void       find_boundary_polys_by_regionID();
static void       find_boundary_polys_by_regionname();
static int        same_material();
static void       sort_segms();
static void       add_to_taillist();
static void       add_to_headlist();
static CNsegmptr  matching_segm();
static CNtriaptr  *create_tria_array();
static void       elem_range_err();
static void       elem_index_err();

void CNmat_boundary();
void CNmat_exp_boundary();

/*********************************************/
/*********  GENERATE A BOUNDARY LINE  ********/
/*********************************************/

/*
 * Generate the boundaries of regions from points
 * This routine replaces CNgenerate_boundary_from_nodes() 
 *
 * This routine works on mesh-datasets as well as quantity datasets.
 * However quantity datasets need a valid parent...
 */
void CNgenerate_boundary_from_points(dptr, verbose)
CNdatasetptr dptr;
int          verbose;
{
   CNdatasetptr dmesh;
   CNregionptr  R;
   CNtriaptr    T;
#ifdef DEBUG
   CNptsegptr   P;
#endif

   /* Error checking */
   if (dptr == NULL) {
      (void) fprintf(stderr,
      "CNgenerate_boundary_from_points:Error - Null dataset!\n");
      return;
   }
   if (dptr->datatype == CN_PIF_PARENT)
      dmesh = dptr;
   else if ((dmesh=dptr->parent) == NULL) {
      (void) fprintf(stderr,
      "CNgenerate_boundary_from_points:Error - Null parent dataset!\n");
      return;
   }
   if (dptr->triahead == NULL) {
      (void) fprintf(stderr,
      "CNgenerate_boundary_from_points:Error - No triangles in dataset!\n");
      return;
   }
   if (dmesh->pointhead == NULL) {
      (void) fprintf(stderr,
      "CNgenerate_boundary_from_points:Error - No points in parent dataset!\n");
      return;
   }

   /*
    * Apply the region properties to the triangles
    * Modify the region properties for Oxide type materials
    * Attach the region pointers to the triangles
    */
   for (R=dptr->regionhead; R!=NULL; R=R->next) {
      if ((strcmp(R->matname,"Oxide")==0) || (strcmp(R->matname,"SiO2")==0))
         R->nocont = CN_TRUE;
      for (T=dptr->triahead; T!=NULL; T=T->next) {
         if (T->region == R->ID) {
            T->nocont = R->nocont;
            T->R      = R;
         }
      }
   }

   /* Generate a point-segment list for the mesh */
   if (dmesh->ptseghead == NULL)
   CNgenerate_ptseg_list(dmesh->triahead, dmesh->triatail,
                         dmesh->pointhead, dmesh->pointtail,
                         &(dmesh->ptseghead), &(dmesh->ptsegtail));
   
   /* Set the colors in the different regions */
   generate_region_colors(dptr->regionhead, dptr->regiontail);

   /* Find the point-segments on the boundaries between regions */
   for (R=dptr->regionhead; R!=NULL; R=R->next) {
      /* Do only those regions that have not been treated yet */
      if (R->polyhead==NULL) {
         if (verbose) CNprint_region(R);
         CNfind_boundary_polys(R, 
                               dmesh->ptseghead, dmesh->ptsegtail,
                               dptr->triahead, dptr->triatail,
                               (dptr==dmesh)?CN_FALSE:CN_TRUE);
      }
   }
}


/* 
 * Set the colors in the different regions 
 */
/*ARGSUSED*/
static void generate_region_colors(regionhead, regiontail)
CNregionptr regionhead, regiontail;
{
#define NCOLORS 6
   CNregionptr R, RA;
   int color_index, samecolor;
   int FOUND;

   /* use only certain colors */
   static int color_array[NCOLORS] = { 6, 7, 8, 9, 2, 3 };

   color_index = 0;
   for (R=regionhead; R!=NULL; R=R->next) {
      /* Check to see if the material has already been specified */
      FOUND = CN_FALSE;
      for (RA=regionhead; RA!=R && RA!=NULL && !FOUND; RA=RA->next)
          if (strcmp(RA->matname,R->matname)==0) {
             FOUND=CN_TRUE;
             samecolor = RA->color;
          }
 
      if (!FOUND) {
         /* 
          * Set the region color 
          * Don't change the color if it has already been specified 
          */
         if (R->color < -1) {
            /* The color is selected from the array */
            R->color = color_array[color_index % NCOLORS];

            /* Increment the color array index if no match was found */
            color_index++;
         }
      } else {
         /* Set the color to the matching region's color */
         R->color = samecolor;
      }
   }
}


/*****************************************************/
/*********  FIND SEGMENTS FROM TRIANGLE LIST  ********/
/*****************************************************/

typedef struct _locpoint_sruct {
   CNpointptr p;
   CNtlistptr tlisthead;
   CNtlistptr tlisttail;
   int        ntria;
} CNlocpoint;

/*
 * Given a list of triangles, generate a list of segments.
 * The objective is to generate a hierarchical list of triangles, segments
 * and points.
 * Segments must not be repeated in the list.
 */
/*ARGSUSED*/
void CNgenerate_ptseg_list(triahead,triatail,
                           pointhead,pointtail,
                           ptseghead,ptsegtail)
CNtriaptr  triahead,   triatail;
CNpointptr pointhead,  pointtail;
CNptsegptr *ptseghead, *ptsegtail;
{
   CNtriaptr  T;
   CNpointptr P;
   CNlocpoint *pointarr;
   int        npoints, ptsegID=0, i;
   unsigned int siz;

   /*
    * Make sure the point-segment list is empty first
    */
   if (*ptseghead != NULL) CNdelete_ptseg_list(ptseghead, ptsegtail);

   /*
    * Reset the triangle and point flags
    */
   for (T=triahead;  T!=NULL; T=T->next) T->flag = CN_FALSE;
   for (P=pointhead; P!=NULL; P=P->next) P->flag = CN_FALSE;

   /* 
    * Allocate space for local storage 
    * The list of triangles belonging to each point is stored in a
    * temporary array.
    */
   npoints = CNcount_points(pointhead, pointtail);
   siz     = (unsigned int)(npoints*sizeof(CNlocpoint));
   if ((pointarr = (CNlocpoint *)malloc(siz))==NULL) {
      (void) fprintf(stderr,"Error - insufficient memory!\n");
      return;
   }

   /* 
    * Fill the point array and use the point flag to index the point array
    */
   npoints=0;
   for (P=pointhead; P!=NULL; P=P->next) {
      pointarr[npoints].p         = P;
      pointarr[npoints].tlisthead = NULL;
      pointarr[npoints].tlisttail = NULL;
      P->flag= npoints;
      npoints++;
   }

   /* 
    * Fill the point array with the point-triangle interconnections
    */
   for (T=triahead; T!=NULL; T=T->next) {
      if ((T->n1->coord == T->n2->coord) ||
          (T->n2->coord == T->n3->coord)) {
         (void) fprintf(stderr,"Error - Tria %d shares 1 or more points ",
                        T->ID);
         (void) fprintf(stderr,"(P%d P%d P%d)\n",
                        T->n1->coord->ID, 
                        T->n2->coord->ID, 
                        T->n3->coord->ID);
      } else {
         add_point(T->n1->coord,T,pointarr,npoints);
         add_point(T->n2->coord,T,pointarr,npoints);
         add_point(T->n3->coord,T,pointarr,npoints);
      }
   }

   /*
    * Now go thru all the triangles again and set up a pt-segm list.
    * shared_triangles() is called to find the number of triangles
    * shared by each pt-segm - this is used as a check for adding pt-segms
    */
   for (T=triahead; T!=NULL; T=T->next) {
      add_ptseg(ptseghead, ptsegtail, T->n1->coord, T->n2->coord, &ptsegID, 
                pointarr, npoints);
      add_ptseg(ptseghead, ptsegtail, T->n2->coord, T->n3->coord, &ptsegID, 
                pointarr, npoints);
      add_ptseg(ptseghead, ptsegtail, T->n1->coord, T->n3->coord, &ptsegID, 
                pointarr, npoints);
      T->flag = CN_TRUE;            /* Flag the triangle */
   }

#ifdef DEBUG
   /*
    * Print the point-segment list
    */
   CNprint_tria_list(triahead, triatail);
   CNprint_ptseg_list(*ptseghead, *ptsegtail);
   for (i=0; i<npoints; i++) {
      (void) fprintf(stdout,"Point ID = %d  ",pointarr[i].p->ID);
      CNprint_tlist(pointarr[i].tlisthead, pointarr[i].tlisttail);
   }
#endif

   /* 
    * Free the tlist
    */
   for (i=0; i<npoints; i++)
      CNdelete_tlist_list(&(pointarr[i].tlisthead), &(pointarr[i].tlisttail));

   /* 
    * Free the local array
    */
   free((char *)pointarr);

   /* 
    * Identify the point-segments on the boundary 
    */
   CNfind_boundary_ptsegs(*ptseghead, *ptsegtail);

}

/* 
 * Add a triangle to the point's triangle list.
 */
/*ARGSUSED*/
static void add_point(P,T,pointarr,npoints)
CNpointptr P;
CNtriaptr  T;
CNlocpoint *pointarr;
int       npoints;
{
   CNinsert_tlist(&(pointarr[P->flag].tlisthead),
                  &(pointarr[P->flag].tlisttail),T);
}

/*
 * Add a pt-segm to the list, but only if the pt-segm does not already
 * exist.  The check is to see if both of the triangles attached to the
 * pt-segm has already been flagged; if that is true, then the pt-segm must
 * already be in the pt-segm list.
 */
/*ARGSUSED*/
static void add_ptseg(ptseghead,ptsegtail,p1,p2,ptsegID,pointarr,npoints)
CNptsegptr *ptseghead, *ptsegtail;
CNpointptr p1, p2;
int        *ptsegID;
CNlocpoint *pointarr;
int        npoints;
{
   CNtriaptr tria_arr[2];
   CNptsegptr sg;
   int       i, ntria = 0;

   /*
    * shared_triangles() is called to find the number of triangles
    * shared by each ptseg - this is used as a check for adding ptsegs
    */
   ntria = shared_triangles(tria_arr, p1, p2, pointarr, npoints);
   if (!shared_tria_is_flagged(tria_arr, ntria)) {
      /* Insert the ptsegent to the list */
      sg = CNinsert_ptseg(ptseghead,ptsegtail, p1, p2, (*ptsegID)++);

      /* Add neighboring triangle info to the segment */
      sg->numtri = ntria;
      for (i=0; i<ntria && i<2; i++)
         sg->nbrtri[i] = tria_arr[i];
   }
}


/*
 * Find the triangles shared by two points (a segm)
 */
/*ARGSUSED*/
static int shared_triangles(tria_arr,p1,p2,pointarr,npoints)
CNtriaptr *tria_arr;
CNpointptr p1, p2;
CNlocpoint *pointarr;
int        npoints;
{
   int ntria=0, k;
   int FOUND = CN_FALSE;
   CNtriaptr  TA, TB;
   CNtlistptr TLA, TLB;


   for (TLA=pointarr[p1->flag].tlisthead; TLA!=NULL; TLA=TLA->next) 
   for (TLB=pointarr[p2->flag].tlisthead; TLB!=NULL; TLB=TLB->next) {
      TA = TLA->T; 
      TB = TLB->T; 
      if (TA == TB) {
         /*
          * This triangle is shared by both points
          * Check the current array to see if the triangle is
          * already in the list
          */
         FOUND = CN_FALSE;
         for (k=0; k<ntria && !FOUND; k++)
            if (tria_arr[k] == TA) FOUND = CN_TRUE;
         if (!FOUND && ntria<2) tria_arr[ntria++] = TA;
      }
   }

#ifdef DEBUG
   /* Print out */
   if (ntria > 0) {
      (void) fprintf(stdout,"Points %d and %d share %d triangles: (",
              p1->ID,p2->ID,ntria);
      for (i=0; i<ntria; i++)
         (void) fprintf(stdout,"%d ",tria_arr[i]->ID);
      (void) fprintf(stdout,")\n");
   }
#endif

   /* Return the number of shared triangles */
   return (ntria);
}


/*
 * Go through the triangles shared by a segm and see if one of
 * the triangles has been flagged.
 */
static int shared_tria_is_flagged(tria_arr,ntria)
CNtriaptr *tria_arr;
int       ntria;
{
   int flag = CN_FALSE;
   int i;

   /* Check the number of shared triangles - valid range : 0 - 2 */
   if (ntria > 2) {
      (void) fprintf(stderr,
                     "Warning - found more than 2 adjacent triangles!\n");
      ntria = 2;
   }

   for (i=0; i<ntria && !flag; i++) {
      if (tria_arr[i]->flag == CN_TRUE) flag = CN_TRUE;
   }

   return(flag);
}


/***************************************************/
/*********  FIND SEGMENTS BOUNDING REGIONS  ********/
/***************************************************/

/*
 * Search for the segments on the boundary of adjoining regions
 */
/*ARGSUSED*/
void CNfind_boundary_ptsegs(ptseghead, ptsegtail)
CNptsegptr ptseghead, ptsegtail;
{
   CNptsegptr S;

   /* Go through a point-segment list and find the triangles on the boundary */
   for (S=ptseghead; S!=NULL; S=S->next) {
      S->boundary = CN_FALSE;

      /* Find out if the segment is a boundary segment */
      if (S->numtri == 0) 
         (void) fprintf(stderr,"Error - Segment %d has no triangles!\n",S->ID);

      else if (S->numtri == 1)
         /* This is a boundary segment - a triangle on the edge */
         S->boundary = CN_TRUE;

      else if ( (S->numtri == 2) &&
                (S->nbrtri[0]->region != S->nbrtri[1]->region) ) {
         /* This is a boundary segment - two adjoining regions */
         S->boundary = CN_TRUE;
      }
   }
}


/***************************************************/
/*********  FIND POLYGON BOUNDING A REGION  ********/
/***************************************************/

/*
 * Generate a polygon list of all the boundaries of a region
 * The triangles contained in the ptseg list belong to the parent.
 * If this is a quantity-derived dataset then we want to use the
 * triangles in the quantity, which should have the same triangle ID's
 */
/*ARGSUSED*/
void CNfind_boundary_polys(R,
                           ptseghead,  ptsegtail, 
                           triahead,   triatail,
                           rematch_trias)
CNregionptr R;
CNptsegptr  ptseghead,  ptsegtail;
CNtriaptr   triahead,   triatail;
int         rematch_trias;
{
   /* Find the region's true boundary */
   find_boundary_polys_by_regionID(ptseghead, ptsegtail, 
                                   triahead, triatail,
                                   &(R->polyhead), &(R->polytail), 
                                   R->ID,
                                   rematch_trias);

   /* Find the material boundary */
   find_boundary_polys_by_regionname(ptseghead, ptsegtail, 
                                   triahead, triatail,
                                   &(R->matpolyhead), &(R->matpolytail), 
                                   R->ID, R->matname,
                                   rematch_trias);
}


/*
 * Generate a polygon list of all the boundaries of a region
 * The triangles contained in the ptseg list belong to the parent.
 * If this is a quantity-derived dataset then we want to use the
 * triangles in the quantity, which should have the same triangle ID's
 *
 * Do the sorting by regionID
 */
/*ARGSUSED*/
static void 
find_boundary_polys_by_regionID(ptseghead, ptsegtail, 
                                triahead, triatail,
                                polyhead, polytail, regID, rematch_trias)
CNptsegptr ptseghead, ptsegtail;
CNtriaptr  triahead, triatail;
CNpolyptr *polyhead, *polytail;
int       regID;
int       rematch_trias;
{
   CNptsegptr P;
   CNsegmptr  segmhead=NULL, segmtail=NULL;
   CNtriaptr  T, T2;
   CNnodeptr  ndarr[10];
   int        nnodes=0;
   CNtriaptr  *triaarr=NULL;
   int        trIDmax=0, trIDmin=0;
   int        ierr=0;

   if (rematch_trias) {
      /*
      (void) printf("rematching triangles...\n");
       */
      /* Create a temp array to store the triangles */
      triaarr = create_tria_array(triahead,triatail,&trIDmax,&trIDmin);
      if (triaarr == NULL) return;
  
      /* Copy the triangles to the array */
      for (T=triahead; T!=NULL; T=T->next)
         if ((T->ID >= trIDmin) && (T->ID <= trIDmax))
            triaarr[T->ID - trIDmin] = T;
   }

   /* 
    * Go thru each point-segment and search for the triangles in the
    * region of interest
    */
   for (P=ptseghead; P!=NULL; P=P->next) {
      /* Act only on the boundary segments */
      if (P->boundary == CN_FALSE) continue;

      T = NULL;
      if ((P->numtri == 1) && (P->nbrtri[0]->region == regID)) {
         T = P->nbrtri[0];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }


      } else if ((P->numtri == 2) && (P->nbrtri[0]->region == regID)) {
         T = P->nbrtri[0];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }

      } else if ((P->numtri == 2) && (P->nbrtri[1]->region == regID)) {
         T = P->nbrtri[1];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }
      }

      if (T!=NULL) {
         nnodes=0;
         /* Find the nodes that match the points on the segment */
         if (T->n1->coord == P->p1 || T->n1->coord == P->p2)
            ndarr[nnodes++] = T->n1;
         if (T->n2->coord == P->p1 || T->n2->coord == P->p2)
            ndarr[nnodes++] = T->n2;
         if (T->n3->coord == P->p1 || T->n3->coord == P->p2)
            ndarr[nnodes++] = T->n3;
         if (nnodes != 2) {
            (void) fprintf(stderr,
            "Warning - found incorrect no of nodes(%d, need 2)!\n",nnodes);
         } else {
            (void) CNinsert_segm(&segmhead, &segmtail, 
                                 ndarr[0], ndarr[1], regID);
         }
      }   
   }

   /* 
    * Now arrange the segments in a polygon 
    */
   sort_segms(&segmhead, &segmtail, polyhead, polytail, regID);

   /*
    * Free the local array
    */
   if (triaarr) free((char *)triaarr);

}

/*
 * Generate a polygon list of all the boundaries of a region
 * The triangles contained in the ptseg list belong to the parent.
 * If this is a quantity-derived dataset then we want to use the
 * triangles in the quantity, which should have the same triangle ID's
 *
 * Do the sorting by regionname
 */
/*ARGSUSED*/
static void 
find_boundary_polys_by_regionname(ptseghead, ptsegtail, 
                                  triahead, triatail,
                                  polyhead, polytail, 
                                  regID, regname, rematch_trias)
CNptsegptr ptseghead, ptsegtail;
CNtriaptr  triahead, triatail;
CNpolyptr *polyhead, *polytail;
int        regID;
char      *regname;
int        rematch_trias;
{
   CNptsegptr P;
   CNsegmptr  segmhead=NULL, segmtail=NULL;
   CNtriaptr  T, T2;
   CNnodeptr  ndarr[10];
   int        nnodes=0;
   CNtriaptr  *triaarr=NULL;
   int        trIDmax=0, trIDmin=0;
   int        ierr=0;

   if (rematch_trias) {
      /*
      (void) printf("rematching triangles...\n");
       */
      /* Create a temp array to store the triangles */
      triaarr = create_tria_array(triahead,triatail,&trIDmax,&trIDmin);
      if (triaarr == NULL) return;
  
      /* Copy the triangles to the array */
      for (T=triahead; T!=NULL; T=T->next)
         if ((T->ID >= trIDmin) && (T->ID <= trIDmax))
            triaarr[T->ID - trIDmin] = T;
   }

   /* 
    * Go thru each point-segment and search for the triangles in the
    * region of interest
    */
   for (P=ptseghead; P!=NULL; P=P->next) {
      /* Act only on the boundary segments */
      if (P->boundary == CN_FALSE) continue;

      /* Filter out segments with triangles which share the same matnames */
      if ((P->numtri == 2) && 
          (same_material(P->nbrtri[0]->R, regname)) &&
          (same_material(P->nbrtri[1]->R, regname)))
         continue;

      T = NULL;
      if ((P->numtri == 1) && (same_material(P->nbrtri[0]->R, regname))) {
         T = P->nbrtri[0];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }


      } else if ((P->numtri == 2) && (same_material(P->nbrtri[0]->R, regname))){
         T = P->nbrtri[0];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }

      } else if ((P->numtri == 2) && (same_material(P->nbrtri[1]->R, regname))){
         T = P->nbrtri[1];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((T2 = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = T2;
         }
      }

      if (T!=NULL) {
         nnodes=0;
         /* Find the nodes that match the points on the segment */
         if (T->n1->coord == P->p1 || T->n1->coord == P->p2)
            ndarr[nnodes++] = T->n1;
         if (T->n2->coord == P->p1 || T->n2->coord == P->p2)
            ndarr[nnodes++] = T->n2;
         if (T->n3->coord == P->p1 || T->n3->coord == P->p2)
            ndarr[nnodes++] = T->n3;
         if (nnodes != 2) {
            (void) fprintf(stderr,
            "Warning - found incorrect no of nodes(%d, need 2)!\n",nnodes);
         } else {
            (void) CNinsert_segm(&segmhead, &segmtail, 
                                 ndarr[0], ndarr[1], regID);
         }
      }   
   }

   /* 
    * Now arrange the segments in a polygon 
    */
   sort_segms(&segmhead, &segmtail, polyhead, polytail, regID);

   /*
    * Free the local array
    */
   if (triaarr) free((char *)triaarr);

}


/*
 * Check that the material names are identical
 */
static int same_material(R, matname)
CNregionptr R;
char        *matname;
{
   /* Error check */
   if ((R == NULL) || (R->matname == NULL) || (matname == NULL)) 
      return(CN_FALSE);

   if (strcmp(R->matname, matname) == 0) 
      return(CN_TRUE);
   else
      return(CN_FALSE);
}

      
/*
 * sort out the list of segments to form a joined curve
 */
static void sort_segms(segmhead,segmtail,polyhead,polytail,regID)
CNsegmptr *segmhead, *segmtail;
CNpolyptr  *polyhead, *polytail;
int        regID;
{
 
   CNsegmptr  S;
   CNnodeptr  n1, n2;
   CNnlistptr nlisthead=NULL, nlisttail=NULL;
   int        pID=0;
 
   while ((S = *segmhead)!=NULL) {
      nlisthead = NULL;
      nlisttail = NULL;
      n1 = S->n1;
      n2 = S->n2;
      CNinsert_tailnlist(&nlisthead, &nlisttail, n1);
      CNinsert_tailnlist(&nlisthead, &nlisttail, n2);
      CNdelete_segm(segmhead, segmtail, S);
      add_to_taillist(segmhead, segmtail, n2, &nlisthead, &nlisttail);
      add_to_headlist(segmhead, segmtail, n1, &nlisthead, &nlisttail);
      (void) CNinsert_poly(polyhead, polytail, 
                           nlisthead, nlisttail, regID, pID++);
   }
}
 
/*
 * add to the tail of the data list
 */
static void
add_to_taillist(segm_listhead, segm_listtail, nd, nlisthead, nlisttail)
CNsegmptr *segm_listhead, *segm_listtail;
CNnodeptr  nd;
CNnlistptr *nlisthead, *nlisttail;
{
   CNsegmptr S;
 
   /* add the further node at every loop iteration */
   while ((S=matching_segm(segm_listhead,nd))!=NULL) {
      if (S->n1 == nd) {
         CNinsert_tailnlist(nlisthead, nlisttail, S->n2);
         nd = S->n2;
      } else {
         CNinsert_tailnlist(nlisthead, nlisttail, S->n1);
         nd = S->n1;
      }
      CNdelete_segm(segm_listhead, segm_listtail, S);
   }
   return;
}

/*
 * add to the head of the data list
 */
static void
add_to_headlist(segm_listhead, segm_listtail, nd, nlisthead, nlisttail)
CNsegmptr *segm_listhead, *segm_listtail;
CNnodeptr  nd;
CNnlistptr *nlisthead, *nlisttail;
{
   CNsegmptr S;
 
   /* add the further node at every loop iteration */
   while ((S=matching_segm(segm_listhead,nd))!=NULL) {
      if (S->n1 == nd) {
         CNinsert_headnlist(nlisthead, nlisttail, S->n2);
         nd = S->n2;
      } else {
         CNinsert_headnlist(nlisthead, nlisttail, S->n1);
         nd = S->n1;
      }
      CNdelete_segm(segm_listhead, segm_listtail, S);
   }
   return;
}

/*
 * Return segm containing node that matches a given node
 * We are looking for the segment that is attached to the current
 * segment
 */
static CNsegmptr matching_segm(segm_listhead,nd)
CNsegmptr *segm_listhead;
CNnodeptr  nd;
{
   CNsegmptr  S,SF=NULL;
   int        FOUND = CN_FALSE;
 
   /* check the segments in the list */
   for (S=(*segm_listhead); S!=NULL && !FOUND; S=S->next) {
      /* check the nodes on either end of the segment */
      if ((S->n1 == nd) || (S->n2 == nd)) {
         FOUND = CN_TRUE;
      }
      if (FOUND) SF = S;
   }
 
   /* return the segm-ptr */
   if (!FOUND) SF = NULL;
   return(SF);
}


/*
 * Utility functions for matching triangles 
 */

/*
 * Create a temp array to store the trias
 */
/*ARGSUSED*/
static CNtriaptr *create_tria_array(triahead,triatail,tmax,tmin)
CNtriaptr triahead, triatail;
int       *tmax, *tmin;
{
   CNtriaptr T, *trarr=NULL;
   int       trIDmin, trIDmax, trrange;
   int       i;
   unsigned  int size;
 
   if (triahead == NULL) return(NULL);
 
   /* Go thru the trialist and check for max ID */
   trIDmin = triahead->ID;
   trIDmax = triahead->ID;
   for (T=triahead; T!=NULL; T=T->next) {
      if (T->ID < trIDmin) trIDmin = T->ID;
      if (T->ID > trIDmax) trIDmax = T->ID;
   }
 
   trrange = trIDmax - trIDmin + 1;
   if (trrange <= 0) {
   (void) fprintf(stderr,
         "Error - The specified range of %s [%d, %d] is too small! (min=%d)!\n",
          "trias", trIDmin, trIDmax, 0);
          return(NULL);
   } else if (trrange > CN_MAXOBJS) {
   (void) fprintf(stderr,
         "Error - The specified range of %s [%d, %d] is too large! (max=%d)!\n",
          "trias", trIDmin, trIDmax, CN_MAXOBJS);
          return(NULL);
   }
 
   /* Calculate the storage requirements */
   size    = (unsigned int)(trrange*sizeof(CNtriaptr));
 
   /* Allocate temp storage space for trias */
   if ((trarr = (CNtriaptr *)malloc(size))==NULL) {
      (void) fprintf(stderr,"Couldn't allocate space for triaptr array\n");
      return(NULL);
   }
 
   /* Fill the array */
   for (i=0; i<trrange; i++) trarr[i] = NULL;
 
   /* Return */
   *tmax = trIDmax;
   *tmin = trIDmin;
   return(trarr);
}
 
/*
 * The element is out of array range
 */
/*ARGSUSED*/
static void elem_range_err(element, ID, minID, maxID, ierr)
char *element;
int   ID, minID, maxID, ierr;
{
   (void) fprintf(stderr,
           "Error - The %s %d is not in the valid range [%d, %d]\n",
           element, ID, minID, maxID);
}
 
/*
 * The element does not exist
 */
/*ARGSUSED*/
static void elem_index_err(element, ID, ierr)
char *element;
int   ID, ierr;
{
   (void) fprintf(stderr,"Error - The %s %d has not been defined!\n",
           element, ID);
}


/******************************************************/
/*********  FIND POLYGON BOUNDING 2 MATERIALS  ********/
/******************************************************/

/*
 * Find the boundaries of 2 adjoining materials
 * (multiple regions might contain similar materials)
 * This routine works on mesh-datasets as well as quantity datasets.
 * However quantity datasets need a valid parent...
 */
void CNmat_boundary(dptr,mat1,mat2,polyhead,polytail)
CNdatasetptr dptr;
char *mat1, *mat2;
CNpolyptr *polyhead, *polytail;
{
   CNdatasetptr dmesh;
   CNregionptr  R;
   CNptsegptr   P;
   CNsegmptr    segmhead=NULL, segmtail=NULL;
   CNtriaptr    T, Ttmp;
   CNtriaptr    *triaarr=NULL;
   int          trIDmax=0, trIDmin=0;
   int          ierr=0;
   int          reg1ID;
   /*
   int          reg2ID;
    */
   int          rematch_trias=CN_FALSE;
   int          MAT1_FOUND=CN_TRUE, MAT2_FOUND=CN_TRUE;
   CNnodeptr    ndarr[10];
   int          nnodes=0;

   /* Error checking */
   if (dptr == NULL) {
      (void) fprintf(stderr,
      "CNmat_boundary : Error - Null dataset!\n");
      return;
   }
   if (dptr->datatype == CN_PIF_PARENT)
      dmesh = dptr;
   else if ((dmesh=dptr->parent) == NULL) {
      (void) fprintf(stderr,
      "CNmat_boundary : Error - Null parent dataset!\n");
      return;
   }
   if (dptr->triahead == NULL) {
      (void) fprintf(stderr,
      "CNmat_boundary : Error - No triangles in dataset!\n");
      return;
   }
   if (dmesh->pointhead == NULL) {
      (void) fprintf(stderr,
      "CNmat_boundary : Error - No points in parent dataset!\n");
      return;
   }

   /* Generate a point-segment list for the mesh */
   if (dmesh->ptseghead == NULL) {
   CNgenerate_ptseg_list(dmesh->triahead,  dmesh->triatail,
                         dmesh->pointhead, dmesh->pointtail,
                         &(dmesh->ptseghead), &(dmesh->ptsegtail));
   }
   
   /*
    * The point-segment list is connected to the triangles of the
    * mesh.  These need to be matched to the quantity's triangles.
    */
   if (dptr != dmesh) rematch_trias = CN_TRUE;
   if (rematch_trias) {
      /* Create a temp array to store the triangles */
      triaarr = create_tria_array(dptr->triahead,dptr->triatail,
                                  &trIDmax,&trIDmin);
      if (triaarr == NULL) return;
  
      /* Copy the triangles to the array */
      for (T=dptr->triahead; T!=NULL; T=T->next)
         if ((T->ID >= trIDmin) && (T->ID <= trIDmax))
            triaarr[T->ID - trIDmin] = T;
   }

#ifdef DEBUG
   nnodes = 0;
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) 
      if (P->boundary == CN_TRUE) nnodes++;
   (void) printf("Found %d boundary segments\n",nnodes);
   nnodes = 0;
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) 
      if (P->boundary == CN_TRUE && P->numtri==2) nnodes++;
   (void) printf("Found %d double-tri boundary segments\n",nnodes);
#endif

   /* 
    * Go thru the segment list and find the segments that match
    * the two material descriptions
    */
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) {
      /* Act only on the boundary segments */
      if (P->boundary == CN_FALSE) continue;

      /* Need 2 adjoining regions */
      if (P->numtri != 2) continue;

      reg1ID = P->nbrtri[0]->region;
      /*
      reg2ID = P->nbrtri[1]->region;
       */

      /* Go thru the regions and find regions that match the regID and mat */
      MAT1_FOUND = CN_FALSE;
      MAT2_FOUND = CN_FALSE;
      for (R=dptr->regionhead; R!=NULL; R=R->next) {
         if ((R->ID == P->nbrtri[0]->region) && 
             (strcmp(R->matname,mat1)==0)) {
            reg1ID = P->nbrtri[0]->region;
            MAT1_FOUND = CN_TRUE;
         } else if ((R->ID == P->nbrtri[1]->region) && 
                    (strcmp(R->matname,mat1)==0)){
            reg1ID = P->nbrtri[1]->region;
            MAT1_FOUND = CN_TRUE;
         } else if ((R->ID == P->nbrtri[0]->region) && 
                    (strcmp(R->matname,mat2)==0)){
            /*
            reg2ID = P->nbrtri[0]->region;
             */
            MAT2_FOUND = CN_TRUE;
         } else if ((R->ID == P->nbrtri[1]->region) && 
                    (strcmp(R->matname,mat2)==0)){
            /*
            reg2ID = P->nbrtri[1]->region;
             */
            MAT2_FOUND = CN_TRUE;
         }
      }

      /* Success!  Add the points to the node-segment list */
      if (MAT1_FOUND && MAT2_FOUND) {
         /* T is the triangle in material 1 */
         T = (P->nbrtri[0]->region == reg1ID) ? P->nbrtri[0] : P->nbrtri[1];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((Ttmp = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = Ttmp;
         }

         if (T != NULL) {
            nnodes=0;
            /* Find the nodes that match the points on the segment */
            if (T->n1->coord == P->p1 || T->n1->coord == P->p2)
               ndarr[nnodes++] = T->n1;
            if (T->n2->coord == P->p1 || T->n2->coord == P->p2)
               ndarr[nnodes++] = T->n2;
            if (T->n3->coord == P->p1 || T->n3->coord == P->p2)
               ndarr[nnodes++] = T->n3;
            if (nnodes != 2) {
               (void) fprintf(stderr,
               "Warning - found incorrect no of nodes(%d, need 2)!\n",nnodes);
            } else {
               (void) CNinsert_segm(&segmhead, &segmtail, 
                                    ndarr[0], ndarr[1], reg1ID);
            }
         }
      }   
   }

   /* 
    * Now arrange the segments in a polygon 
    */
   sort_segms(&segmhead, &segmtail, polyhead, polytail, reg1ID);

#ifdef DEBUG
   /* print out the segment list */
   CNprint_segm_list(segmhead, segmtail);

   /* print out the polygon list */
   CNprint_poly_list(*polyhead, *polytail);
#endif

   /*
    * Free the local array
    */
   if (triaarr) free((char *)triaarr);
}



/****************************************************/
/*********  FIND POLYGON BOUNDING A MATERIAL ********/
/****************************************************/

/*
 * Find the exposed boundary of a material
 * i.e. the boundary of the material which does NOT touch adjacent materials.
 * (multiple regions might contain similar materials)
 * This routine works on mesh-datasets as well as quantity datasets.
 * However quantity datasets need a valid parent...
 */
void CNmat_exp_boundary(dptr,mat1,polyhead,polytail)
CNdatasetptr dptr;
char *mat1;
CNpolyptr *polyhead, *polytail;
{
   CNdatasetptr dmesh;
   CNregionptr  R;
   CNptsegptr   P;
   CNsegmptr    segmhead=NULL, segmtail=NULL;
   CNtriaptr    T, Ttmp;
   CNtriaptr    *triaarr=NULL;
   int          trIDmax=0, trIDmin=0;
   int          ierr=0;
   int          reg1ID;
   int          rematch_trias=CN_FALSE;
   int          MAT1_FOUND=CN_TRUE;
   CNnodeptr    ndarr[10];
   int          nnodes=0;

   /* Error checking */
   if (dptr == NULL) {
      (void) fprintf(stderr,
      "CNmat_exp_boundary : Error - Null dataset!\n");
      return;
   }
   if (dptr->datatype == CN_PIF_PARENT)
      dmesh = dptr;
   else if ((dmesh=dptr->parent) == NULL) {
      (void) fprintf(stderr,
      "CNmat_exp_boundary : Error - Null parent dataset!\n");
      return;
   }
   if (dptr->triahead == NULL) {
      (void) fprintf(stderr,
      "CNmat_exp_boundary : Error - No triangles in dataset!\n");
      return;
   }
   if (dmesh->pointhead == NULL) {
      (void) fprintf(stderr,
      "CNmat_exp_boundary : Error - No points in parent dataset!\n");
      return;
   }

   /* Generate a point-segment list for the mesh */
   if (dmesh->ptseghead == NULL) {
   CNgenerate_ptseg_list(dmesh->triahead,  dmesh->triatail,
                         dmesh->pointhead, dmesh->pointtail,
                         &(dmesh->ptseghead), &(dmesh->ptsegtail));
   }
   
   /*
    * The point-segment list is connected to the triangles of the
    * mesh.  These need to be matched to the quantity's triangles.
    */
   if (dptr != dmesh) rematch_trias = CN_TRUE;
   if (rematch_trias) {
      /* Create a temp array to store the triangles */
      triaarr = create_tria_array(dptr->triahead,dptr->triatail,
                                  &trIDmax,&trIDmin);
      if (triaarr == NULL) return;
  
      /* Copy the triangles to the array */
      for (T=dptr->triahead; T!=NULL; T=T->next)
         if ((T->ID >= trIDmin) && (T->ID <= trIDmax))
            triaarr[T->ID - trIDmin] = T;
   }

#ifdef DEBUG
   nnodes = 0;
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) 
      if (P->boundary == CN_TRUE) nnodes++;
   (void) printf("Found %d boundary segments\n",nnodes);
   nnodes = 0;
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) 
      if (P->boundary == CN_TRUE && P->numtri==2) nnodes++;
   (void) printf("Found %d double-tri boundary segments\n",nnodes);
#endif

   /* 
    * Go thru the segment list and find the segments that match
    * the material description
    */
   for (P=dmesh->ptseghead; P!=NULL; P=P->next) {
      /* Act only on the boundary segments */
      if (P->boundary == CN_FALSE) continue;

      /* Need only 1 material (no adjoining triangles) */
      if (P->numtri != 1) continue;

      reg1ID = P->nbrtri[0]->region;

      /* Go thru the regions and find regions that match the regID and mat */
      MAT1_FOUND = CN_FALSE;
      for (R=dptr->regionhead; R!=NULL; R=R->next) {
         if ((R->ID == P->nbrtri[0]->region) && 
             (strcmp(R->matname,mat1)==0)) {
            reg1ID = P->nbrtri[0]->region;
            MAT1_FOUND = CN_TRUE;
         }
      }

      /* Success!  Add the points to the node-segment list */
      if (MAT1_FOUND) {
         /* T is the triangle in material 1 */
         T = P->nbrtri[0];
         if (T!=NULL && rematch_trias) {
            if ((T->ID < trIDmin) && (T->ID > trIDmax))
               elem_range_err("tria",T->ID,trIDmin,trIDmax,ierr++);
            else if ((Ttmp = triaarr[T->ID-trIDmin]) == NULL)
               elem_index_err("tria",T->ID,ierr++);
            else
               T = Ttmp;
         }

         if (T != NULL) {
            nnodes=0;
            /* Find the nodes that match the points on the segment */
            if (T->n1->coord == P->p1 || T->n1->coord == P->p2)
               ndarr[nnodes++] = T->n1;
            if (T->n2->coord == P->p1 || T->n2->coord == P->p2)
               ndarr[nnodes++] = T->n2;
            if (T->n3->coord == P->p1 || T->n3->coord == P->p2)
               ndarr[nnodes++] = T->n3;
            if (nnodes != 2) {
               (void) fprintf(stderr,
               "Warning - found incorrect no of nodes(%d, need 2)!\n",nnodes);
            } else {
               (void) CNinsert_segm(&segmhead, &segmtail, 
                                    ndarr[0], ndarr[1], reg1ID);
            }
         }
      }   
   }

   /* 
    * Now arrange the segments in a polygon 
    */
   sort_segms(&segmhead, &segmtail, polyhead, polytail, reg1ID);

#ifdef DEBUG
   /* print out the segment list */
   CNprint_segm_list(segmhead, segmtail);

   /* print out the polygon list */
   CNprint_poly_list(*polyhead, *polytail);
#endif

   /*
    * Free the local array
    */
   if (triaarr) free((char *)triaarr);
}

