/* GTS - Library for the manipulation of triangulated surfaces
 * Copyright (C) 1999 Stéphane Popinet
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <math.h>
#include <stdlib.h>
#include <locale.h>
#include "config.h"
#ifdef HAVE_GETOPT_H
#  include <getopt.h>
#endif /* HAVE_GETOPT_H */
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include "gts.h"

#ifndef PI
# define PI 3.14159265359
#endif

typedef enum { NONE, 
	       QUALITY, 
	       AREA, 
	       EPV, 
	       INCOMP, 
	       FOLD, 
	       HEIGHT, 
	       CURVATURE, 
	       GAUSSIAN,
               COMPONENT } ColorOptions;

ColorOptions color = NONE;
gboolean autoscale = TRUE;
gboolean boundary = FALSE;
gboolean non_manifold = FALSE;
gboolean duplicate = FALSE;
gint flatten = -1;
guint height = 0;
gdouble avg = 0.;
gdouble min = 0.0, max = 1.0;
gdouble maxcosine2 = 1.0;
gboolean verbose = FALSE;
gboolean faces = FALSE;

typedef struct {
  gdouble r, g, b;
} Color;

typedef struct {
  GPtrArray * colors;
  gboolean reversed;
} Colormap;

Colormap * colormap = NULL;

static Color * color_new (gdouble r, gdouble g, gdouble b)
{
  Color * c = g_malloc (sizeof (Color));
  c->r = r; c->g = g; c->b = b;
  return c;
}

static Colormap * colormap_read (FILE * fptr)
{
  Colormap * cmap = g_malloc (sizeof (Colormap));
  Color c;
  cmap->reversed = FALSE;
  cmap->colors = g_ptr_array_new ();
  while (fscanf (fptr, "%lf %lf %lf", &c.r, &c.g, &c.b) == 3)
    g_ptr_array_add (cmap->colors, color_new (c.r/255., c.g/255., c.b/255.));
  return cmap;
}

static Colormap * colormap_jet (void)
{
  Colormap * cmap = g_malloc (sizeof (Colormap));
  gint i;

  cmap->reversed = FALSE;
  cmap->colors = g_ptr_array_new ();
  for (i = 0; i < 127; i++) {
    gdouble r = 
      i <= 46 ? 0. : 
      i >= 111 ? -0.03125*(i - 111) + 1. :
      i >= 78 ? 1. : 
      0.03125*(i - 46);
    gdouble g = 
      i <= 14 || i >= 111 ? 0. : 
      i >= 79 ? -0.03125*(i - 111) : 
      i <= 46 ? 0.03125*(i - 14) : 
      1.;
    gdouble b =
      i >= 79 ? 0. :
      i >= 47 ? -0.03125*(i - 79) :
      i <= 14 ? 0.03125*(i - 14) + 1.:
      1.;

    g_ptr_array_add (cmap->colors, color_new (r, g, b));
  }
  return cmap;
}

static Color colormap_color (Colormap * cmap, gdouble val)
{
  Color c = {1., 1., 1.}, * c1, * c2;
  guint i, n;
  gdouble coef;

  g_return_val_if_fail (cmap != NULL, c);

  if (val > 1.0) val = 1.0;
  else if (val < 0.0) val = 0.0;
  if (cmap->reversed)
    val = 1.0 - val;

  n = cmap->colors->len;
  if (n == 0)
    return c;
  if (n == 1)
    return *((Color *)cmap->colors->pdata[0]);

  i = floor((gdouble)val*(gdouble)(n - 1));
  if (i == n - 1)
    return *((Color *)cmap->colors->pdata[cmap->colors->len - 1]);
  coef = val*(gdouble)(n - 1) - (gdouble)i;
  c1 = cmap->colors->pdata[i];
  c2 = cmap->colors->pdata[i+1];
  c.r = c1->r + coef*(c2->r - c1->r);
  c.g = c1->g + coef*(c2->g - c1->g);
  c.b = c1->b + coef*(c2->b - c1->b);
  return c;
}

static void write_point (GtsPoint * p, FILE * fp)
{
  fprintf (fp, "%f %f %f", 
	   flatten == 0 ? avg : p->x,
	   flatten == 1 ? avg : p->y,
	   flatten == 2 ? avg : p->z);
}

static void foreach_vertex (GtsVertex * v, gpointer * info) 
{
  FILE * fp = info[0];
  GHashTable * hash = info[1];
  guint * nv = info[2];
  GtsSurface * s = info[4];

  write_point (GTS_POINT (v), fp);
  switch (color) {
  case EPV: {
    Color c = colormap_color (colormap,
		((gdouble)g_slist_length (v->segments) - min)/(max - min));
    fprintf (fp, " %g %g %g 1.0\n", c.r, c.g, c.b);
    break;
  }
  case HEIGHT: {
    Color c = colormap_color (colormap,
	       ((&GTS_POINT (v)->x)[height] - avg - min)/(max - min));
    fprintf (fp, " %g %g %g 1.0\n", c.r, c.g, c.b);
    break;
  }
  case CURVATURE: {
    gdouble c = 0.;
    Color color;
    GtsVector n;

    if (!gts_vertex_is_boundary (v, s) &&
	gts_vertex_mean_curvature_normal (v, s, n)) 
      c = gts_vector_norm (n)/2.;
    color = colormap_color (colormap, (c - min)/(max - min));
    fprintf (fp, " %g %g %g 1.0\n", color.r, color.g, color.b);
    break;
  }
  case GAUSSIAN: {
    gdouble gc = 0.;
    Color color;

    if (!gts_vertex_is_boundary (v, s))
      gts_vertex_gaussian_curvature (v, s, &gc);
    color = colormap_color (colormap, (gc - min)/(max - min));
    fprintf (fp, " %g %g %g 1.0\n", color.r, color.g, color.b);
    break;
  }
  default:
    fprintf (fp, "\n");
  }
  g_hash_table_insert (hash, v, GUINT_TO_POINTER (++(*nv)));
}

static void foreach_boundary_edge (GtsEdge * e, gpointer info)
{
  FILE * fp = info;

  if (gts_edge_is_boundary (e, NULL)) {
    fputs ("VECT 1 2 0 2 0 ", fp);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v1), fp);
    fputc (' ', fp);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v2), fp);
    fputc ('\n', fp);
  }
}

static void foreach_feature_edge (GtsEdge * e, gdouble * angle)
{
  if (g_slist_length (e->triangles) == 2 &&
      fabs (gts_triangles_angle (e->triangles->data, e->triangles->next->data))
      < *angle) {
    fputs ("VECT 1 2 0 2 0 ", stdout);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v1), stdout);
    fputc (' ', stdout);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v2), stdout);
    fputc ('\n', stdout);
  }
}

static void foreach_non_manifold_edge (GtsEdge * e, gpointer info)
{
  FILE * fp = info;

  if (g_slist_length (e->triangles) > 2) {
    fputs ("VECT 1 2 0 2 0 ", fp);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v1), fp);
    fputc (' ', fp);
    write_point (GTS_POINT (GTS_SEGMENT (e)->v2), fp);
    fputc ('\n', fp);
  }
}

static void foreach_duplicate_edge (GtsSegment * s, gpointer info)
{
  FILE * fp = info;

  if (gts_segment_is_duplicate (s)) {
    fputs ("VECT 1 2 0 2 0 ", fp);
    write_point (GTS_POINT (s->v1), fp);
    fputc (' ', fp);
    write_point (GTS_POINT (s->v2), fp);
    fputc ('\n', fp);
  }
}

static void foreach_triangle (GtsTriangle * t, gpointer * info)
{
  FILE * fp = info[0];
  GHashTable * hash = info[1];
  guint * nfold = info[3];
  guint p1 = 0, p2 = 0, p3 = 0;

  p1 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, GTS_SEGMENT (t->e1)->v1));
  if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v1) {
    p2 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e2)->v2));
    p3 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e1)->v2));
    }
  else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v2) {
    p2 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e1)->v2));
    p3 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e2)->v1));
  }
  else if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v2) {
    p2 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e2)->v1));
    p3 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e1)->v2));
  }
  else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v1) {
    p2 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e1)->v2));
    p3 = GPOINTER_TO_UINT (g_hash_table_lookup (hash, 
						GTS_SEGMENT (t->e2)->v2));
  }
  else
    g_assert_not_reached ();
  g_return_if_fail (p1 && p2 && p3);
  fprintf (fp, "3 %u %u %u", p1 - 1, p2 - 1, p3 - 1);
  switch (color) {
  case QUALITY: {
    Color c = colormap_color (colormap, 
			      (gts_triangle_quality (t) - min)/(max - min));
    fprintf (fp, " %g %g %g\n", c.r, c.g, c.b);
    break;
  }
  case AREA: {
    Color c = colormap_color (colormap, 
			      (gts_triangle_area (t) - min)/(max - min));
    fprintf (fp, " %g %g %g\n", c.r, c.g, c.b);
    break;
  }
  case INCOMP: {
    gboolean compatible = TRUE;
    GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3;
    GSList * i = e1->triangles;
    while (compatible && i) {
      GtsTriangle * t1 = i->data;
      if (t1 != t && !gts_triangles_are_compatible (t, t1, e1))
	compatible = FALSE;
      i = i->next;
    }
    i = e2->triangles;
    while (compatible && i) {
      GtsTriangle * t1 = i->data;
      if (t1 != t && !gts_triangles_are_compatible (t, t1, e2))
	compatible = FALSE;
      i = i->next;
    }
    i = e3->triangles;
    while (compatible && i) {
      GtsTriangle * t1 = i->data;
      if (t1 != t && !gts_triangles_are_compatible (t, t1, e3))
	compatible = FALSE;
      i = i->next;
    }
    if (!compatible)
      fputs (" 1. 0. 0.", fp);
    fputc ('\n', fp);
    break;
  }
  case FOLD: {
    gboolean fold = FALSE;
    GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3;
    fold = gts_triangles_are_folded (e1->triangles, 
				     GTS_SEGMENT (e1)->v1,
				     GTS_SEGMENT (e1)->v2,
				     maxcosine2);
    if (!fold)
      fold = gts_triangles_are_folded (e2->triangles, 
				       GTS_SEGMENT (e2)->v1,
				       GTS_SEGMENT (e2)->v2,
				       maxcosine2);
    if (!fold)
      fold = gts_triangles_are_folded (e3->triangles, 
				       GTS_SEGMENT (e3)->v1,
				       GTS_SEGMENT (e3)->v2,
				       maxcosine2);      
    if (fold) {
      (*nfold)++;
      fputs (" 1. 0. 0.", fp);
    }
    fputc ('\n', fp);
  }
  default:
    fputc ('\n', fp);
  }
}

static void min_max_curvature (GtsVertex * v, gpointer * data)
{
  gdouble * min = data[0];
  gdouble * max = data[1];
  GtsSurface * s = data[2];
  GtsVector n;
  gdouble c = 0.;

  if (!gts_vertex_is_boundary (v, s) && 
      gts_vertex_mean_curvature_normal (v, s, n)) 
    c = gts_vector_norm (n)/2.;
  if (c < *min) *min = c;
  if (c > *max) *max = c;
}

static void min_max_gaussian (GtsVertex * v, gpointer * data)
{
  gdouble * min = data[0];
  gdouble * max = data[1];
  GtsSurface * s = data[2];
  gdouble gc = 0.;

  if (!gts_vertex_is_boundary (v, s))
    gts_vertex_gaussian_curvature (v, s, &gc);
  if (gc < *min) *min = gc;
  if (gc > *max) *max = gc;
}

static void oogl_surface (GtsSurface * s, FILE * fptr)
{
  gpointer info[5];
  GtsSurfaceStats stats;
  GtsSurfaceQualityStats qstats;
  guint np = 0, nfold = 0;

  g_return_if_fail (s != NULL);
  g_return_if_fail (fptr != NULL);

  info[0] = fptr;
  info[1] = g_hash_table_new (NULL, NULL);
  info[2] = &np;
  info[3] = &nfold;
  info[4] = s;

  gts_surface_stats (s, &stats);
  switch (color) {
  case AREA:
    if (autoscale) {
      gts_surface_quality_stats (s, &qstats);
      min = qstats.face_area.min;
      max = qstats.face_area.max;
    }
    break;
  case QUALITY:
    if (autoscale) {
      gts_surface_quality_stats (s, &qstats);
      min = qstats.face_quality.min;
      max = qstats.face_quality.max;
    }
    break;
  case EPV:
    if (autoscale) {
      min = stats.edges_per_vertex.min;
      max = stats.edges_per_vertex.max;
    }
    break;
  case HEIGHT:
    if (autoscale) {
      GtsBBox * bbox = gts_bbox_surface (gts_bbox_class (), s);

      min = (&bbox->x1)[height] - avg;
      max = (&bbox->x2)[height] - avg;
      gts_object_destroy (GTS_OBJECT (bbox));
    }
    break;
  case CURVATURE:
    if (autoscale) {
      gpointer data[3];

      min = G_MAXDOUBLE;
      max = - G_MAXDOUBLE;
      data[0] = &min;
      data[1] = &max;
      data[2] = s;
      gts_surface_foreach_vertex (s, (GtsFunc) min_max_curvature, data);
    }
    break;
  case GAUSSIAN:
    if (autoscale) {
      gpointer data[3];

      min = G_MAXDOUBLE;
      max = - G_MAXDOUBLE;
      data[0] = &min;
      data[1] = &max;
      data[2] = s;
      gts_surface_foreach_vertex (s, (GtsFunc) min_max_gaussian, data);
    }
    break;
  default:
    ;
  }
  if (verbose)
    fprintf (stderr, "scalar min: %f max: %f\n", min, max);
  if (max == min)
    max = min + 1.;
  if (color == EPV || color == HEIGHT || 
      color == CURVATURE || color == GAUSSIAN)
    fputs ("COFF ", fptr);
  else
    fputs ("OFF ", fptr);
  fprintf (fptr, "%u %u %u\n",
	   stats.edges_per_vertex.n, 
	   stats.n_faces,
	   stats.faces_per_edge.n);
  gts_surface_foreach_vertex (s, (GtsFunc) foreach_vertex, info);
  gts_surface_foreach_face (s, (GtsFunc) foreach_triangle, info);
  if (verbose && color == FOLD)
    fprintf (stderr, "# Number of folds: %u\n", nfold);

  g_hash_table_destroy (info[1]);
}

static GtsVertex * next_vertex (GtsVertex * v)
{
  GSList * j = v->segments;

  while (j) {
    GtsSegment * s = j->data;

    if (GTS_OBJECT (s)->reserved == s) {
      GTS_OBJECT (s)->reserved = NULL;
      return s->v1 != v ? s->v1 : s->v2;
    }
    j = j->next;
  }

  return NULL;
}

static GSList * chain_segments (GSList * segments)
{
  GSList * chains = NULL, * i;
  
  i = segments;
  while (i) {
    GTS_OBJECT (i->data)->reserved = i->data;
    i = i->next;
  }

  i = segments;
  while (i) {
    GtsSegment * s = i->data;

    if (GTS_OBJECT (s)->reserved == s) {
      GSList * chain = g_slist_prepend (NULL, s->v1);
      GtsVertex * v = s->v2;

      GTS_OBJECT (s)->reserved = NULL;
      while (v) {
	chain = g_slist_prepend (chain, v);
	v = next_vertex (v);
      }
      chains = g_slist_prepend (chains, chain);
    }
    i = i->next;
  }

  return chains;
}

static void write_isolines (GtsSurface * surface, 
			    GNode * stree,
			    gdouble level,
			    FILE * fp,
			    gboolean gnuplot)
{
  GNode * ptree;
  GtsSurface * plane;
  GtsVertex * v1, * v2, * v3;
  GSList * inter, * chains, * i;
  guint nvertices = 0;

  g_return_if_fail (surface != NULL);
  g_return_if_fail (stree != NULL);
  g_return_if_fail (fp != NULL);

  plane = gts_surface_new (gts_surface_class (),
			   gts_face_class (),
			   gts_edge_class (),
			   gts_vertex_class ());
  v1 = gts_vertex_new (gts_vertex_class (), -1e10, -1e10, level);
  v2 = gts_vertex_new (gts_vertex_class (), 1e10, -1e10, level);
  v3 = gts_vertex_new (gts_vertex_class (), 0., 1e10, level);
  gts_surface_add_face (plane, gts_face_new (gts_face_class(),
			       gts_edge_new (gts_edge_class (), v1, v2),
			       gts_edge_new (gts_edge_class (), v2, v3),
			       gts_edge_new (gts_edge_class (), v3, v1)));
  ptree = gts_bb_tree_surface (plane);
  inter = gts_surface_intersection (surface, plane, stree, ptree);
  chains = chain_segments (inter);

  i = chains;
  while (i) {
    GSList * chain = i->data;
    nvertices += g_slist_length (chain);
    i = i->next;
  }

  if (!gnuplot) {
    fprintf (fp, "VECT %d %d 0\n", g_slist_length (chains), nvertices);
    i = chains;
    while (i) {
      GSList * chain = i->data;
      
      fprintf (fp, "%d ", g_slist_length (chain));
      i = i->next;
    }
    i = chains;
    while (i) {
      fprintf (fp, "0 ");
      i = i->next;
    }
    fprintf (fp, "\n");
  }
  i = chains;
  while (i) {
    GSList * chain = i->data;
    GSList * j = chain;
    
    while (j) {
      GtsPoint * p = j->data;

      write_point (p, fp);
      fputc ('\n', fp);
      j = j->next;
    }
    if (gnuplot)
      fputc ('\n', fp);
    g_slist_free (chain);
    i = i->next;
  }
  g_slist_free (chains);
  g_slist_free (inter);
  gts_bb_tree_destroy (ptree, TRUE);
  gts_object_destroy (GTS_OBJECT (plane));
}

static void sum_flatten (GtsPoint * p)
{
  avg += (&p->x)[flatten];
}

static void write_edge (GtsSegment * s)
{
  printf ("%f %f %f\n%f %f %f\n\n",
	  GTS_POINT (s->v1)->x, GTS_POINT (s->v1)->y, GTS_POINT (s->v1)->z,
	  GTS_POINT (s->v2)->x, GTS_POINT (s->v2)->y, GTS_POINT (s->v2)->z);
}

static void write_face (GtsTriangle * t)
{
  GtsVertex * v1, * v2, * v3;
  GtsPoint o;
 
  gts_triangle_vertices (t, &v1, &v2, &v3);
  o.x = (GTS_POINT (v1)->x + GTS_POINT (v2)->x + GTS_POINT (v3)->x)/3.;
  o.y = (GTS_POINT (v1)->y + GTS_POINT (v2)->y + GTS_POINT (v3)->y)/3.;
  o.z = (GTS_POINT (v1)->z + GTS_POINT (v2)->z + GTS_POINT (v3)->z)/3.;
  printf ("OFF 3 1 3\n"
	  "%f %f %f\n"
	  "%f %f %f\n"
	  "%f %f %f\n"
	  "3 0 1 2\n",
	  o.x + 0.9*(GTS_POINT (v1)->x - o.x),
	  o.y + 0.9*(GTS_POINT (v1)->y - o.y),
	  o.z + 0.9*(GTS_POINT (v1)->z - o.z),
	  o.x + 0.9*(GTS_POINT (v2)->x - o.x),
	  o.y + 0.9*(GTS_POINT (v2)->y - o.y),
	  o.z + 0.9*(GTS_POINT (v2)->z - o.z),
	  o.x + 0.9*(GTS_POINT (v3)->x - o.x),
	  o.y + 0.9*(GTS_POINT (v3)->y - o.y),
	  o.z + 0.9*(GTS_POINT (v3)->z - o.z));
}

int main (int argc, char * argv[])
{
  GtsSurface * surface;
  gboolean nosurface = FALSE;
  int c = 0;
  guint isolines = 0;
  GtsFile * fp;
  gdouble feature = 0.;
  gdouble isoline[256];
  guint niso = 0;
  gboolean gnuplot = FALSE;

  if (!setlocale (LC_ALL, "POSIX"))
    g_warning ("cannot set locale to POSIX");

  colormap = colormap_jet (); /* default */
  while (c != EOF) {
#ifdef HAVE_GETOPT_LONG
    static struct option long_options[] = {
      {"gnuplot", no_argument, NULL, 'G'},
      {"faces", no_argument, NULL, 't'},
      {"isoline", required_argument, NULL, 'I'},
      {"gaussian", no_argument, NULL, 'g'},
      {"curvature", no_argument, NULL, 'C'},
      {"feature", required_argument, NULL, 'e'},
      {"flatten", required_argument, NULL, 'F'},
      {"component", no_argument, (int *)&color, COMPONENT},
      {"quality", no_argument, (int *)&color, QUALITY},
      {"area", no_argument, (int *)&color, AREA},
      {"incomp", no_argument, (int *)&color, INCOMP},
      {"fold", required_argument, NULL, 'f'},
      {"epv", no_argument, (int *)&color, EPV},
      {"height", required_argument, NULL, 'H'},
      {"boundary", no_argument, &boundary, TRUE},
      {"non-manifold", no_argument, &non_manifold, TRUE},
      {"duplicate", no_argument, &duplicate, TRUE},
      {"isolines", required_argument, NULL, 'i'},
      {"cmap", required_argument, NULL, 'c'},
      {"help", no_argument, NULL, 'h'},
      {"min", required_argument, NULL, 'm'},
      {"max", required_argument, NULL, 'M'},
      {"nosurface", no_argument, NULL, 'n'},
      {"reverse", no_argument, NULL, 'r'},
      {"verbose", no_argument, NULL, 'v'},
      { NULL }
    };
    int option_index = 0;
    switch ((c = getopt_long (argc, argv, "c:hm:M:rvf:i:nF:e:H:CgI:tG",
			      long_options, &option_index))) {
#else /* not HAVE_GETOPT_LONG */
    switch ((c = getopt (argc, argv, "c:hm:M:rvf:i:nF:e:H:CgI:tG"))) {
#endif /* not HAVE_GETOPT_LONG */
    case 'G': /* gnuplot */
      gnuplot = TRUE;
      break;
    case 't': /* faces */
      faces = TRUE;
      break;
    case 'I': /* isoline */
      isoline[niso++] = atof (optarg);
      break;
    case 'g': /* gaussian */
      color = GAUSSIAN;
      break;
    case 'C': /* curvature */
      color = CURVATURE;
      break;
    case 'H': /* height */
      color = HEIGHT;
      height = atoi (optarg);
      if (height > 2)
	height = 2;
      break;
    case 'e': /* feature */
      feature = atof (optarg)*PI/180.;
      break;
    case 'F': /* flatten */
      flatten = atoi (optarg);
      if (flatten < 0.) flatten = 0;
      else if (flatten > 2) flatten = 2;
      break;
    case 'c': { /* colormap file */
      FILE * fptr = fopen (optarg, "rt");
      if (!fptr) {
	fprintf (stderr, "%s: cannot open colormap file `%s'.\n",
		 argv[0], optarg);
	return 1;
      }
      colormap = colormap_read (fptr);
      fclose (fptr);
      break;
    }
    case 'i': /* isolines */
      isolines = atoi (optarg);
      break;
    case 'f': /* fold */
      color = FOLD;
      maxcosine2 = cos (atof (optarg)*PI/180.);
      maxcosine2 *= maxcosine2;
      break;
    case 'm': /* minimum value */
      autoscale = FALSE;
      min = atof (optarg);
      break;
    case 'M': /* maximum value */
      autoscale = FALSE;
      max = atof (optarg);
      break;
    case 'n': /* nosurface */
      nosurface = TRUE;
      break;
    case 'r': /* reverse colormap */
      colormap->reversed = TRUE;
      break;
    case 'h': /* help */
      fprintf (stderr,
             "Usage: gts2oogl [OPTION]... < input.srf > output.oogl\n"
	     "Convert a GTS file (surface file format generated by the Gts Library)\n"
	     "to OOGL file format (Geomview).\n"
	     "\n"
             "  -G      --gnuplot      writes isolines in gnuplot format\n"   
             "          --component    color faces according to the component\n"
             "                         they belong too\n"
	     "          --quality      color faces according to their quality\n"
	     "          --area         color faces according to their area\n"
	     "          --incomp       color incompatible faces\n"
	     "  -f VAL  --fold=VAL     color faces which make an angle smaller\n" 
	     "                         than VAL degrees with any of their neighbors\n"
             "  -t      --faces        output individual faces\n"
	     "          --epv          color vertices according to number of edges per vertex\n"
	     "  -H C    --height=C     color vertices according to their C coordinate\n"
	     "  -g      --gaussian     color vertices according to Gaussian curvature\n"
	     "  -C      --curvature    color vertices according to mean curvature\n"
	     "          --boundary     output boundary edges\n"
             "  -e A    --feature=A    output `feature' edges defined by angle A\n"
             "          --non-manifold output non-manifold edges\n"
             "          --duplicate    output duplicate edges\n"
	     "  -i N    --isolines=N   draw N isolines (levels of constant altitude)\n"
             "  -I L    --isolines=L   draw isoline at level L\n"
	     "          --cmap=FILE    load FILE as colormap\n"
	     "  -m VAL, --min=VAL      use VAL as minimum scaling value\n"
	     "  -M VAL, --max=VAL      use VAL as maximum scaling value\n"
	     "  -r,     --reverse      reverse colormap\n"
             "  -n      --nosurface    do not output surface\n"
	     "  -F C    --flatten=C    set C coordinate to average value\n"
	     "  -v,     --verbose      display surface statistics\n"
	     "  -h,     --help         display this help and exit\n"
	     "\n"
	     "Report bugs to %s\n",
	     GTS_MAINTAINER);
      return 0;
      break;
    case 'v':
      verbose = TRUE;
      break;
    case '?': /* wrong options */
      fprintf (stderr, "Try `gts2oogl --help' for more information.\n");
      return 1;
    }
  }

  surface = gts_surface_new (gts_surface_class (),
			     gts_face_class (),
			     gts_edge_class (),
			     gts_vertex_class ());
  fp = gts_file_new (stdin);
  if (gts_surface_read (surface, fp)) {
    fputs ("gts2oogl: the file on standard input is not a valid GTS file\n", 
	   stderr);
    fprintf (stderr, "stdin:%d:%d: %s\n", fp->line, fp->pos, fp->error);
    return 1;
  }
  
  if (verbose)
    gts_surface_print_stats (surface, stderr);

  if (flatten >= 0) {
    gts_surface_foreach_vertex (surface, (GtsFunc) sum_flatten, NULL);
    if (avg != 0.)
      avg /= gts_surface_vertex_number (surface);
  }

  if (boundary || non_manifold || duplicate || 
      isolines || feature > 0. || niso > 0) {
    if (!nosurface) {
      puts ("(geometry \"surface\" { = ");
      oogl_surface (surface, stdout);
      puts ("})");
    }
    if (boundary) {
      if (!nosurface || isolines || niso > 0 || non_manifold || duplicate)
	puts ("(geometry \"boundary\" { = ");
      puts ("LIST {");
      gts_surface_foreach_edge (surface, (GtsFunc) foreach_boundary_edge, 
				stdout);
      puts ("}");
      if (!nosurface || isolines || niso > 0 || non_manifold || duplicate)
	puts ("})");
    }
    if (feature > 0.) {
      if (!nosurface || isolines || niso > 0 || non_manifold || duplicate)
	puts ("(geometry \"feature\" { = ");
      puts ("LIST {");
      gts_surface_foreach_edge (surface, (GtsFunc) foreach_feature_edge, 
				&feature);
      puts ("}");
      if (!nosurface || isolines || niso > 0 || non_manifold || duplicate)
	puts ("})");
    }
    if (non_manifold) {
      if (!nosurface || isolines || niso > 0 || boundary || duplicate)
	puts ("(geometry \"non-manifold\" { = ");
      puts ("LIST {");
      gts_surface_foreach_edge (surface, (GtsFunc) foreach_non_manifold_edge, 
				stdout);
      puts ("}");
      if (!nosurface || isolines || niso > 0 || boundary || duplicate)
	puts ("})");
    }
    if (duplicate) {
      if (!nosurface || isolines || niso > 0 || boundary || non_manifold)
	puts ("(geometry \"duplicate\" { = ");
      puts ("LIST {");
      gts_surface_foreach_edge (surface, (GtsFunc) foreach_duplicate_edge, 
				stdout);
      puts ("}");
      if (!nosurface || isolines || niso > 0 || boundary || non_manifold)
	puts ("})");
    }
    if (isolines > 0) {
      GNode * stree;
      gdouble z, dz;
      GtsBBox * bbox;
      guint i;

      stree = gts_bb_tree_surface (surface);
      bbox = stree->data;
      dz = (bbox->z2 - bbox->z1)/(isolines + 1);
      z = bbox->z1 + dz;
      if (!nosurface || boundary)
	puts ("(geometry \"isolines\" { = ");
      if (!gnuplot)
	puts ("LIST {");
      for (i = 0; i < isolines; i++, z += dz)
	write_isolines (surface, stree, z, stdout, gnuplot);
      if (!gnuplot)
	puts ("}");
      if (!nosurface || boundary)
	puts ("})\n");
      gts_bb_tree_destroy (stree, TRUE);
    }
    else if (niso > 0) {
      GNode * stree;
      GtsBBox * bbox;
      guint i;

      stree = gts_bb_tree_surface (surface);
      bbox = stree->data;
      if (!nosurface || boundary)
	puts ("(geometry \"isolines\" { = ");
      if (!gnuplot)
	puts ("LIST {");
      for (i = 0; i < niso; i++)
	write_isolines (surface, stree, isoline[i], stdout, gnuplot);
      if (!gnuplot)
	puts ("}");
      if (!nosurface || boundary)
	puts ("})\n");
      gts_bb_tree_destroy (stree, TRUE);
    }
  }
  else if (color == COMPONENT) {
    GSList * i = gts_surface_split (surface);

    if (verbose)
      fprintf (stderr, "%u components\n", g_slist_length (i));

    printf ("LIST {\n");
    while (i) {
      gdouble 
	r = rand()/(gdouble)RAND_MAX, 
	g = rand()/(gdouble)RAND_MAX, 
	b = rand()/(gdouble)RAND_MAX;

      printf ("geom { appearance { material {\n"
	      "  ambient %g %g %g\n"
	      "  diffuse %g %g %g\n"
	      "}}\n", 
	      r, g, b, r, g, b);
      gts_surface_write_oogl (i->data, stdout);
      printf ("}\n");
      i = i->next;
    }
    printf ("}\n");
  }
  else if (!nosurface) {
    if (min == max) 
      max = min + 1.;
    if (faces) {
      printf ("LIST {\n");
      gts_surface_foreach_face (surface, (GtsFunc) write_face, NULL);
      printf ("}\n");
    }
    else if (gnuplot)
      gts_surface_foreach_edge (surface, (GtsFunc) write_edge, NULL);
    else
      oogl_surface (surface, stdout);
  }

  return 0;
}
