/******************************************************************************
 *
 * ALBERTA:  an Adaptive multi Level finite element toolbox  using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques
 *
 * www.alberta-fem.de
 *
 ******************************************************************************
 *
 * File:     geomview-graphics.c
 *
 * Description: very simplistic online-graphics with Geomview, we use
 *              togeomview to actually pipe data to geomview.
 *
 ******************************************************************************
 *
 *  author:     Claus-Justus Heine
 *              Abteilung fuer Angewandte Mathematik
 *              Albert-Ludwigs-Universitaet Freiburg
 *              Hermann-Herder-Str. 10
 *              79104 Freiburg
 *              Germany
 *              Claus.Heine@Mathematik.Uni-Freiburg.DE
 *
 *  (c) by C.-J. Heine (2006-2007)
 *
 ******************************************************************************/

#define _XOPEN_SOURCE 600
#define _XOPEN_SOURCE_EXTENDED

#include <alberta.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h> /* for htonl */
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "geomview-graphics.h" /* to keep the proto-type consistent */

#define DEBUG_TOGEOMVIEW 0

#define GV_BINARY false /* define to false for debugging purposes */

static FILE *global_gvf;
static pid_t gv_pid = -1;

#define DEG_MAX 4

#define GVDIM (DIM_OF_WORLD < 3 ? 3 : DIM_OF_WORLD)

typedef REAL GVPOINT[GVDIM];
typedef REAL RGBA[4];

typedef const REAL *(*VERTEX_COLOR_FCT)(GVPOINT gvpt,
					RGBA color,
					const EL_INFO *el_info,
					const REAL_D world,
					const REAL_B lambda,
					void *ud);
typedef void (*EL_COLOR_FCT)(RGBA color, const EL_INFO *el_info, void *ud);

typedef void (*mesh2off_func)(FILE *gvf,
			      MESH *mesh,
			      VERTEX_COLOR_FCT vertex_color_fct,
			      void *v_ud,
			      FLAGS v_fill_flags,
			      EL_COLOR_FCT el_color_fct,
			      void *el_ud,
			      FLAGS el_fill_flags,
			      int ref_deg,
			      bool flat_shading,
			      bool binary);

static const char *gvpoint_fmt = "%e %e %e"
#if GVDIM > 3
  " %e"
#endif
#if GVDIM > 4
  " %e"
#endif
#if GVDIM > 5
  " %e"
#endif
#if GVDIM > 6
  " %e"
#endif
#if GVDIM > 7
  " %e"
#endif
#if GVDIM > 8
  " %e"
#endif
#if GVDIM > 9
# error Maximum is DIM_OF_WORLD == 9
#endif
  ;
#if GVDIM == 3
# define EXPAND_GVDIM(pt) (pt)[0], (pt)[1], (pt)[2]
#else
# define EXPAND_GVDIM(pt) EXPAND_DOW(pt)
#endif

static const RGBA blue  = { 0, 0, 1, 1 };
static const RGBA black = { 0, 0, 0, 1 };
static const RGBA white = { 1, 1, 1, 1 };
 
static const REAL_B vlambda[N_VERTICES_LIMIT] = {
  INIT_BARY_3D(1,0,0,0),
  INIT_BARY_3D(0,1,0,0),
  INIT_BARY_3D(0,0,1,0),
  INIT_BARY_3D(0,0,0,1)
};

static const int lagrange_1_net_1d[][N_VERTICES_1D] = {
  { 0, 1 }
};
static const int lagrange_2_net_1d[][N_VERTICES_1D] = {
  { 0, 2 }, { 2, 1 }
};
static const int lagrange_3_net_1d[][N_VERTICES_1D] = {
  { 0, 2 }, { 2, 3 }, { 3, 1 }
};
static const int lagrange_4_net_1d[][N_VERTICES_1D] = {
  { 0, 2 }, { 2, 3 }, { 3, 4 }, { 4, 1 }
};

static const int lagrange_1_net_2d[][N_VERTICES_2D] = {
  { 0, 1, 2 }
};
static const int lagrange_2_net_2d[][N_VERTICES_2D] = {
  { 0, 5, 4 }, { 5, 3, 4 }, { 1, 3, 5 },
  { 4, 3, 2 }
};
static const int lagrange_3_net_2d[][N_VERTICES_2D] = {
  { 0, 7, 6 }, { 7, 9, 6 }, { 7, 8, 9 }, {8, 3, 9 }, { 8, 1, 3 },
  { 6, 9, 5 }, { 9, 4, 5 }, { 9, 3, 4 },
  { 5, 4, 2 }
};
static const int lagrange_4_net_2d[][N_VERTICES_2D] = {
  { 0, 9, 8},{ 9,12, 8},{ 9,10,12},{10,13,12},{10,11,13},{11, 3,13},{11, 1, 3},
  { 8,12, 7},{12,14, 7},{12,13,14},{13, 4,14},{13, 3, 4},
  { 7,14, 6},{14, 5, 6},{14, 4, 5},
  { 6, 5, 2}
};

static const int lagrange_1_net_3d[][N_VERTICES_3D] = {
  { 0, 1, 2, 3 }
};
static const int lagrange_2_net_3d[][N_VERTICES_3D] = {
  /* first layer */
  { 0, 4, 5, 6 }, { 4, 5, 6, 8 }, { 5, 6, 9, 8 },
  /*           */ { 5, 9, 8, 7 }, { 4, 5, 8, 7 },
  { 4, 1, 7, 8 }, { 5, 7, 2, 9 },
  /* second layer */
  { 6, 8, 9, 3 }
};
static const int lagrange_3_net_3d[][N_VERTICES_3D] = {
  /* first layer */
  {  0,  4,  6,  8 }, {  4,  6,  8, 18 }, {  6,  8, 17, 18 },
  { 16, 17, 18, 19 }, { 17, 18, 19,  6 }, {  18, 19, 4,  6 },
  /* */
  {  4,  5, 19, 18 }, {  5, 19, 18, 12 }, { 19, 18, 16, 12 },
  /*               */ { 16, 12, 10, 19 }, { 12, 10,  5, 19 },
  /* */
  {  6, 19,  7, 17 }, { 19,  7, 17, 16 }, {  7, 17, 14, 16 },
  /*               */ { 14, 16, 11,  7 }, { 16, 11, 19,  7 },
  /* */
  {  5,  1, 10, 12 }, { 19, 10, 11, 16 }, {  7, 11,  2, 14 },
  /* second layer */
  {  8, 18, 17,  9 }, { 18, 17,  9, 13 }, { 17,  9, 15, 13 },
  /*               */ { 15, 13, 16, 17 }, { 13, 16, 18, 17 },
  /* */
  { 18, 12, 16, 13 }, { 17, 16, 14, 15 },
  /* third layer */
  {  9, 13, 15,  3 }
};
static const int lagrange_4_net_3d[][N_VERTICES_3D] = {
  /* first layer */
  {  0,  4,  7, 10 }, {  4,  7, 10, 28 }, {  7, 10, 25, 28 },
  { 34, 25, 28, 31 }, {  7, 25, 28, 31 }, {  4,  7, 28, 31 },
  /* */
  {  4,  5, 31, 28 }, {  5, 31, 28, 29 }, { 31, 28, 34, 29 },
  { 22, 34, 29, 32 }, { 31, 34, 29, 32 }, {  5, 31, 29, 32 },
  /* */
  {  7, 31,  8, 25 }, { 31,  8, 25, 34 }, {  8, 25, 26, 34 },
  { 23, 26, 34, 33 }, {  8, 26, 34, 33 }, { 31,  8, 34, 33 },
  /* */
  {  5,  6, 32, 29 }, {  6, 32, 29, 16 }, { 32, 29, 22, 16 },
  /*               */ { 32, 22, 16, 13 }, {  6, 32, 16, 13 },
  /* */
  { 31, 32, 33, 34 }, { 32, 33, 34, 22 }, { 33, 34, 23, 22 },
  /*               */ { 33, 23, 22, 14 }, { 32, 33, 22, 14 },
  /* */
  {  8, 33,  9, 26 }, { 33,  9, 26, 23 }, {  9, 26, 19, 23 },
  /*               */ {  9, 19, 23, 15 }, { 33,  9, 23, 15 },
  {  6,  1, 13, 16 }, { 32, 13, 14, 22 },
  { 33, 14, 15, 23 }, {  9, 15,  2, 19 },
  /* second layer */
  { 10, 28, 25, 11 }, { 28, 25, 11, 30 }, { 25, 11, 27, 30 },
  { 24, 27, 30, 34 }, { 25, 27, 30, 34 }, { 28, 25, 30, 34 },
  /* */
  { 28, 29, 34, 30 }, { 29, 34, 30, 17 }, { 34, 30, 24, 17 },
  /*               */ { 34, 24, 17, 22 }, { 29, 34, 17, 22 },
  /* */
  { 25, 34, 26, 27 }, { 34, 26, 27, 24 }, { 26, 27, 20, 24 },
  /*               */ { 26, 20, 24, 23 }, { 34, 26, 24, 23 },
  { 29, 16, 22, 17 }, { 34, 22, 23, 24 }, { 26, 23, 19, 20 },
  /* third layer */
  { 11, 30, 27, 12 }, { 30, 27, 12, 18 }, { 27, 12, 21, 18 },
  /*               */ { 27, 21, 18, 24 }, { 30, 27, 18, 24 },
  { 30, 17, 24, 18 }, { 27, 24, 20, 21 },
  /* fourth layer */
  { 12, 18, 21,  3 }
};

#define N_ELEMS(array) (sizeof(array)/sizeof(*array))

static const int (*lagrange_net_1d[])[N_VERTICES_1D] = {
  NULL,
  lagrange_1_net_1d, lagrange_2_net_1d, lagrange_3_net_1d, lagrange_4_net_1d
};
static const int lagrange_nel_1d[] = {
  0,
  N_ELEMS(lagrange_1_net_1d),
  N_ELEMS(lagrange_2_net_1d),
  N_ELEMS(lagrange_3_net_1d),
  N_ELEMS(lagrange_4_net_1d)
};

static const int (*lagrange_net_2d[])[N_VERTICES_2D] = {
  NULL,
  lagrange_1_net_2d, lagrange_2_net_2d, lagrange_3_net_2d, lagrange_4_net_2d
};
static const int lagrange_nel_2d[] = {
  0,
  N_ELEMS(lagrange_1_net_2d),
  N_ELEMS(lagrange_2_net_2d),
  N_ELEMS(lagrange_3_net_2d),
  N_ELEMS(lagrange_4_net_2d)
};

static const int (*lagrange_net_3d[])[N_VERTICES_3D] = {
  NULL,
  lagrange_1_net_3d, lagrange_2_net_3d, lagrange_3_net_3d, lagrange_4_net_3d
};
static const int lagrange_nel_3d[] = {
  0,
  N_ELEMS(lagrange_1_net_3d),
  N_ELEMS(lagrange_2_net_3d),
  N_ELEMS(lagrange_3_net_3d),
  N_ELEMS(lagrange_4_net_3d)
};

/* Compute a sub-division face-list for 3d. This is quite brute-force
 * but so what. We make sure that the sub-faces located on the real
 * faces come first, in the correct ordering.
 *
 * This piece of code is incredibly ugly ...
 */
static int compare_faces(const void *vf1, const void *vf2)
{
  const int *f1 = (const int *)vf1;
  const int *f2 = (const int *)vf2;
    
  return (f1[0] == f2[0]
	  ? (f1[1] == f2[1]
	     ? (f1[2] > f2[2]) - (f1[2] < f2[2])
	     : (f1[1] > f2[1]) - (f1[1] < f2[1]))
	  : (f1[0] > f2[0]) - (f1[0] < f2[0]));
}

static const int (*generate_face_list(int degree))[N_VERTICES_2D]
{
  const BAS_FCTS *bfcts = get_lagrange(3, degree);
  const REAL_B *nodes = LAGRANGE_NODES(bfcts);
  int n_els = lagrange_nel_3d[degree];
  int n_face_faces = lagrange_nel_2d[degree];
  int n_faces = (4 * n_els + 4 * n_face_faces) / 2;
  int (*face_list)[N_VERTICES_2D];
  int tmp_list[n_els*4][N_VERTICES_2D];
  int i, j, int_pos, face_pos[N_FACES_3D];
  
  face_list = (int (*)[N_VERTICES_2D])MEM_ALLOC(n_faces*N_VERTICES_2D, int);

  for (i = 0; i < n_els; i++) {
    for (j = 0; j < N_FACES_3D; j++) {
      int *face = tmp_list[i*N_FACES_3D + j];
      
      face[0] = lagrange_net_3d[degree][i][(j+1) % N_VERTICES_3D];
      face[1] = lagrange_net_3d[degree][i][(j+2) % N_VERTICES_3D];
      face[2] = lagrange_net_3d[degree][i][(j+3) % N_VERTICES_3D];

      if (face[0] > face[1]) {
	int swap = face[0]; face[0] = face[1]; face[1] = swap;
      }
      if (face[1] > face[2]) {
	int swap = face[1]; face[1] = face[2]; face[2] = swap;
      }
      if (face[0] > face[1]) {
	int swap = face[0]; face[0] = face[1]; face[1] = swap;
      }
    }
  }
  qsort((void *)tmp_list, 4*n_els, N_VERTICES_2D*sizeof(int),
	compare_faces);

  i = 0;
  int_pos = 4*n_face_faces;
  for (j = 0; j < N_FACES_3D; j++) {
    face_pos[j] = j*n_face_faces;
  }
  for (i = 0; i < 4*n_els;) {
    int *f1, *f2;
    
    f1 = tmp_list[i];
    if (i < 4*n_els - 1) {
      f2 = tmp_list[i+1];
    
      if (compare_faces(f1, f2) == 0) {

	if (int_pos >= n_faces) {
	  ERROR_EXIT("Not so many faces.\n");
	}

	/* interior face */
	memcpy(face_list[int_pos], f1, N_VERTICES_2D*sizeof(int));
	int_pos++;
	i += 2;
	continue;
      }
    }
    
    /* "boundary" face, determine the face it belongs to */
    for (j = 0; j < N_FACES_3D; j++) {
      if (nodes[f1[0]][j] == 0.0 && 
	  nodes[f1[1]][j] == 0.0 && 
	  nodes[f1[2]][j] == 0.0) {
	memcpy(face_list[face_pos[j]], f1, N_VERTICES_2D*sizeof(int));
	face_pos[j]++;
	i++;
	break;
      }
    }
    if (j == N_FACES_3D) {
      ERROR_EXIT("Face not found.\n");
    }
  }

  if (int_pos != n_faces) {
    ERROR_EXIT("Not enough interior faces.\n");
  }
  for (j = 0; j < N_FACES_3D; j++) {
    if (face_pos[j] != (j+1)*n_face_faces) {
      ERROR_EXIT("Not enough boundary faces.\n");
    }
  }
  
  return (const int (*)[N_VERTICES_2D])face_list;
}

static void make_logo(FILE *gvf)
{
#include "alberta-logo.h"

  fprintf(gvf,
	  "(read geometry { define ALBERTA_LOGO \n"
	  "  INST\n"
	  "  location screen\n"
	  "  origin ndc -1 -1 -0.999\n"
	  "  transform  64 0 0 0  0 64 0 0  0 0 0 0  0 0 0 1\n"
	  "  geom {\n"
	  "    appearance {\n"
	  "      *+shading constant\n"
	  "      *+texturing\n"
	  "      *+transparent\n"
	  "      *-mipmap\n"    /* logo has already the correct size */
	  "      *-mipinterp\n"
	  "      *-linear\n"
	  "      texture {\n"
	  "        apply replace\n"
	  "        clamp none\n"
	  "        image {\n"
	  "          width 64\n"
	  "          height 64\n"
	  "          data RGBA "ALBERTA_LOGO_FMT" %d {\n",
	  (unsigned int)ALBERTA_LOGO_SIZE);
  fwrite(ALBERTA_LOGO_DATA, ALBERTA_LOGO_SIZE, 1, gvf);
  /* ??? error handling ??? */
  fprintf(gvf, "\n"
	  "          }\n"
	  "        }\n"
	  "      }\n"
	  "    }\n"
	  "    STOFF 4 1 -1\n"
	  "    0 0 0  0 0\n"
	  "    1 0 0  1 0\n"
	  "    1 1 0  1 1\n"
	  "    0 1 0  0 1\n"
	  "    4  0 1 2 3\n"
	  "  }\n"
	  "})\n");
  fprintf(gvf, "(new-alien ALBERTA-Logo { : ALBERTA_LOGO })\n");

  fflush(gvf);
}

union htonf
{
  float f; /* hopefully a float is really 4 bytes ... */
  int   i;
};

static inline void fwrite_rgba(const RGBA color, bool binary, FILE *gvf)
{
  if (binary) {
    union htonf hton;
    int rgba_be[4], i;
    
    for (i = 0; i < 4; i++) {
      hton.f = (float)color[i];
      rgba_be[i] = htonl(hton.i);
    }
    fwrite(rgba_be, sizeof(rgba_be), 1, gvf);
  } else {
    fprintf(gvf, " %e %e %e %e", color[0], color[1], color[2], color[3]);
  }
}

static inline void fwrite_triangle(int i0, int i1, int i2, const RGBA rgba,
				   bool binary, FILE *gvf)
{
  if (binary) {
    int elem_be[1+N_VERTICES_2D+1];

    elem_be[0] = htonl(3);
    elem_be[4] = rgba ? htonl(4) : htonl(0);
    elem_be[1] = htonl(i0);
    elem_be[2] = htonl(i1);
    elem_be[3] = htonl(i2);
    fwrite(elem_be, sizeof(elem_be), 1, gvf);
    if (rgba) {
      fwrite_rgba(rgba, true, gvf);
    }
  } else {
    fprintf(gvf, "3 %d %d %d", i0, i1, i2);
    if (rgba) {
      fwrite_rgba(rgba, false, gvf);
    }
    fprintf(gvf, "\n");
  }
}

static inline void
fwrite_vertex(GVPOINT v, const RGBA rgba, bool binary, FILE *gvf)
{
  if (binary) {
    union htonf hton;
    int coords_be[GVDIM];
    int j;

    for (j = 0; j < GVDIM; j++) {
      hton.f = (float)v[j];
      coords_be[j] = htonl(hton.i);
    }
    fwrite(coords_be, sizeof(coords_be), 1, gvf);
    if (rgba) {
      fwrite_rgba(rgba, true, gvf);
    }
  } else {
    fprintf(gvf, gvpoint_fmt, EXPAND_GVDIM(v));
    if (rgba) {
      fwrite_rgba(rgba, false, gvf);
    }
    fprintf(gvf, "\n");
  }
}

static inline void fwrite_line(int nv, int *vidx, const RGBA rgba,
			       bool binary, FILE *gvf)
{
  int i;

  if (binary) {
    int line_be[nv + 2];
    
    line_be[0]    = htonl(nv);
    line_be[nv+1] = htonl(rgba ? 4 : 0);

    for (i = 0; i < nv; i++) {
      line_be[i+1] = htonl(vidx[i]);
    }
    fwrite(line_be, sizeof(line_be), 1, gvf);
    if (rgba) {
      fwrite_rgba(rgba, true, gvf);
    }
  } else {
    fprintf(gvf, "%d ", nv);
    for (i = 0; i < nv; i++) {
      fprintf(gvf, "%d ", vidx[i]);
    }
    if (rgba) {
      fwrite_rgba(rgba, false, gvf);
    }
    fprintf(gvf, "\n");
  }
}

/**Generate Geomview OOGL for the given mesh, using colors to display
 * data. This is the 1d version; DIM_OF_WORLD is arbitrary.
 *
 * @param[in] gvf File descriptor for the conversation with Geomview.
 *
 * @param[in] mesh To underlying ALBERTA mesh.
 *
 * @param[in] vertex_color_fct A function for the computation of per-vertex
 *                 colors.
 *
 * @param[in] v_ud User-data for @a vertex_color_fct.
 *
 * @param[in] v_fill_flags Fill-flags needed by @a vertex_color_fct
 *
 * @param[in] el_color_fct A function for the computation of per-element colors,
 *                  e.g. for estimator values.
 *
 * @param[in] el_ud Data-pointer for use by @a el_color_fct
 *
 * @param[in] el_fill_flags Fill-flags needed by @a el_color_fct
 *
 * @param[in] ref_deg Draw the Lagrange-mesh corresponding to @a
 *                  ref_deg, i.e. do @b not refine @a ref_deg times,
 *                  rather @a ref_deg-1 is the number of vertices on
 *                  each edge.
 */
void mesh2off_1d(FILE *gvf,
		 MESH *mesh,
		 VERTEX_COLOR_FCT vertex_color_fct,
		 void *v_ud,
		 FLAGS v_fill_flags,
		 EL_COLOR_FCT el_color_fct,
		 void *el_ud,
		 FLAGS el_fill_flags,
		 int ref_deg,
		 bool flat_shading,
		 bool binary)
{
  DOF_REAL_D_VEC *coords;
  DOF_INT_VEC *idx;
  const FE_SPACE *fe_space;
  const BAS_FCTS *bas_fcts = NULL;
  int i, vidx;
  const int (*lagrange_net)[N_VERTICES_1D] = NULL;
  int lagrange_nel = -1;

  TEST_EXIT(sizeof(float) == 4, "FIXME, sizeof(float) != 4.\n");
  TEST_EXIT(sizeof(int) == 4, "FIXME, sizeof(int) != 4.\n");

  fprintf(gvf, "appearance {\n");
  if (flat_shading) {
    fprintf(gvf, "\tshading flat\n");
    fprintf(gvf, "\t+vect\n");
  } else {
    fprintf(gvf, "\tshading vcflat\n");
    fprintf(gvf, "\t+vect\n");
  }
  fprintf(gvf,
	  "lighting {\n"
	  "  ambient 0.2 0.2 0.2\n"
	  "  replacelights\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.750000 0.750000 0.750000\n"
	  "    position 0.000000 0.000000 10.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.600000 0.600000 0.600000\n"
	  "    position 0.000000 1.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.400000 0.400000 0.400000\n"
	  "    position 1.000000 -2.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "}\n");
  fprintf(gvf, "}\n"); /* appearance */

  if (DIM_OF_WORLD > 3) {
    fprintf(gvf, "{\nCnSKEL %d%s\n", DIM_OF_WORLD, binary ? " BINARY" : "");
  } else {    
    fprintf(gvf, "{\nCSKEL%s\n", binary ? " BINARY" : "");
  }

  if (mesh->parametric) {
    bool not_all, fast = false;

    not_all = mesh->parametric->not_all && !ref_deg == 1;

    if ((coords = get_lagrange_coords(mesh)) != NULL) {

      fe_space = coords->fe_space;
      bas_fcts = fe_space->bas_fcts;

      fast = ref_deg == 1 || ref_deg == bas_fcts->degree;
    }

    if (fast) {
      DOF dofs[bas_fcts->n_bas_fcts];
      const REAL_B *nodes;
      int nv, nel;

      idx = get_dof_int_vec("idx", fe_space);
      FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

      lagrange_net = lagrange_net_1d[ref_deg];
      lagrange_nel = lagrange_nel_1d[ref_deg];      

      /* Be careful: periodic meshes must not use mesh->n_vertices */
      nel = mesh->n_elements;
      if (ref_deg == 1) {
	if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
	  nv  = mesh->per_n_vertices;
	} else {
	  nv  = mesh->n_vertices;
	}
      } else if (not_all) {
	DOF dofs[bas_fcts->n_bas_fcts];

	nv = 0;
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	  bool affine;

	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);

	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
	    if (affine && i >= N_VERTICES_1D) {
	      break;
	    }
	    if (idx->vec[dofs[i]] != -1) {
	      continue;
	    }
	    idx->vec[dofs[i]] = nv++;
	  }

	} TRAVERSE_NEXT();
	FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
      } else {
 	nv  = coords->fe_space->admin->used_count;
      }

      if (!binary) {
	fprintf(gvf, "%d %d\n", nv, nel);
      } else {
	int duplet[2];
	
	duplet[0] = htonl(nv);
	duplet[1] = htonl(nel);
	fwrite(duplet, sizeof(duplet), 1, gvf);
      }

      nodes = LAGRANGE_NODES(bas_fcts);

      /* <<< emit vertex coordinates */
      vidx = 0;
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|v_fill_flags) {
	const REAL *rgba;
	RGBA color;
	GVPOINT gvpt;
	bool affine;

	if (not_all || (v_fill_flags & FILL_COORDS)) {
	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	}

	if (!not_all) {
	  affine = ref_deg == 1;
	}

	GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
	  if (affine && i >= N_VERTICES_1D) {
	    break;
	  }
	  if (idx->vec[dofs[i]] != -1) {
	    continue;
	  }
	  idx->vec[dofs[i]] = vidx++;
	  rgba = vertex_color_fct(
	    gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	  fwrite_vertex(gvpt, rgba, binary, gvf);
	}
      } TRAVERSE_NEXT();
      /* >>> */

      /* <<< emit element definitions */
      if (!binary) {
	/* <<< ascii version */
	DOF dofs[bas_fcts->n_bas_fcts];
	RGBA rgba;

	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	  bool affine;

	  if (not_all || (el_fill_flags & FILL_COORDS)) {
	    affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = ref_deg == 1;
	  }

	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);
	
	  if (affine) {
	    fprintf(gvf, "2 %d %d", idx->vec[dofs[0]], idx->vec[dofs[1]]);
	    if (el_color_fct) {
	      el_color_fct(rgba, el_info, el_ud);
	      fwrite_rgba(rgba, false, gvf);
	    }
	    fprintf(gvf, "\n");
	  } else {
	    fprintf(gvf, "%d ", lagrange_nel+1);
	    for (i = 0; i < lagrange_nel; i++) {
	      fprintf(gvf, "%d ", idx->vec[dofs[lagrange_net[i][0]]]);
	    }
	    fprintf(gvf, "%d", idx->vec[dofs[lagrange_net[lagrange_nel-1][1]]]);
	    if (el_color_fct) {
	      el_color_fct(rgba, el_info, el_ud);
	      fwrite_rgba(rgba, false, gvf);
	    }
	    fprintf(gvf, "\n");
	  }
	} TRAVERSE_NEXT();
	/* >>> */
      } else { /* binary format in BIG-ENDIAN */
	/* <<< binary BIG-ENDIAN format */
	int n_colors;
	int aff_elem_be[N_VERTICES_1D+2];
	int param_elem_be[lagrange_nel+3];

	n_colors = el_color_fct ? htonl(4) : htonl(0);

	if (not_all || ref_deg == 1) {
	  aff_elem_be[0] = htonl(2);
	  aff_elem_be[3] = n_colors;
	}
	if (ref_deg > 1) {
	  param_elem_be[0] = htonl(lagrange_nel+1);
	  param_elem_be[lagrange_nel+2] = n_colors;
	}

	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	  DOF  dofs[bas_fcts->n_bas_fcts];
	  RGBA rgba;
	  bool affine;

	  if (not_all || (el_fill_flags & FILL_COORDS)) {
	    affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = ref_deg == 1;
	  }

	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  if (affine) {
	    aff_elem_be[1] = htonl(idx->vec[dofs[0]]);
	    aff_elem_be[2] = htonl(idx->vec[dofs[1]]);
	    fwrite(aff_elem_be, sizeof(aff_elem_be), 1, gvf);
	    if (el_color_fct) {
	      el_color_fct(rgba, el_info, el_ud);
	      fwrite_rgba(rgba, true, gvf);
	    }
	  } else {
	    for (i = 0; i < lagrange_nel; i++) {
	      param_elem_be[i+1] = htonl(idx->vec[dofs[lagrange_net[i][0]]]);
	    }
	    param_elem_be[i+1] =
	      htonl(idx->vec[dofs[lagrange_net[lagrange_nel-1][1]]]);
	    fwrite(param_elem_be, sizeof(param_elem_be), 1, gvf);
	    if (el_color_fct) {
	      el_color_fct(rgba, el_info, el_ud);
	      fwrite_rgba(rgba, true, gvf);
	    }
	  }
	} TRAVERSE_NEXT();
	/* >>> */
      } /* binary */
      /* >>> */

      fprintf(gvf, "}\n"); /* SKEL object */

      free_dof_int_vec(idx);

    } else { /* slow parametric path */
      ERROR_EXIT("not yet implemented.\n");
    }
  } else { /* non-parametric version */
    int n_dof[N_NODE_TYPES] = { 1, 0, 0 };
    int node = mesh->node[VERTEX];
    int n0;
    int i;

    if (!binary) {
      fprintf(gvf, "%d %d\n", mesh->n_vertices, mesh->n_elements);
    } else {
      int duplet[2];

      duplet[0] = htonl(mesh->n_vertices);
      duplet[1] = htonl(mesh->n_elements);
      fwrite(duplet, sizeof(duplet), 1, gvf);
    }

    fe_space =
      get_dof_space(mesh, "geomview vertex space", n_dof, ADM_FLAGS_DFLT);
    n0 = fe_space->admin->n0_dof[VERTEX];

    idx = get_dof_int_vec("idx", fe_space);
    FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

    vidx = 0;
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS|v_fill_flags) {
      GVPOINT gvpt;
      RGBA color;
      const REAL *rgba;

      for (i = 0; i < N_VERTICES_1D; i++) {
	if (idx->vec[el_info->el->dof[node+i][n0]] == -1) {
	  idx->vec[el_info->el->dof[node+i][n0]] = vidx++;
	  rgba = vertex_color_fct(
	    gvpt, color, el_info, el_info->coord[i], vlambda[i], v_ud);
	  fwrite_vertex(gvpt, rgba, binary, gvf);
	}
      }
    } TRAVERSE_NEXT();
    
    if (!binary) {
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
      
	fprintf(gvf, "2 %d %d",
		idx->vec[el_info->el->dof[node+0][n0]],
		idx->vec[el_info->el->dof[node+1][n0]]);
	if (el_color_fct) {
	  RGBA rgba;

	  el_color_fct(rgba, el_info, el_ud);
	  fwrite_rgba(rgba, false, gvf);
	}
	fprintf(gvf, "\n");
	
      } TRAVERSE_NEXT();
    } else {
      int elem_be[1+N_VERTICES_1D+1];
      
      elem_be[0] = htonl(2);
      elem_be[3] = el_color_fct ? htonl(4) : htonl(0);

      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	elem_be[1] = htonl(idx->vec[el_info->el->dof[node+0][n0]]);
	elem_be[2] = htonl(idx->vec[el_info->el->dof[node+1][n0]]);
	fwrite(elem_be, sizeof(elem_be), 1, gvf);
	if (el_color_fct) {
	  RGBA rgba;

	  el_color_fct(rgba, el_info, el_ud);
	  fwrite_rgba(rgba, true, gvf);
	}
		
      } TRAVERSE_NEXT();

    }

    fprintf(gvf, "}\n"); /* SKEL object */

    free_dof_int_vec(idx);
    free_fe_space((FE_SPACE *)fe_space);
  }

  /* Done with this object */
  fflush(gvf);
}


/**Generate Geomview OOGL for the given mesh, using colors to display
 * data. This is the 2d version; DIM_OF_WORLD is arbitrary.
 *
 * @param[in] gvf File descriptor for the conversation with Geomview.
 *
 * @param[in] mesh To underlying ALBERTA mesh.
 *
 * @param[in] vertex_color_fct A function for the computation of per-vertex
 *                 colors.
 *
 * @param[in] v_ud User-data for @a vertex_color_fct.
 *
 * @param[in] v_fill_flags Fill-flags needed by @a vertex_color_fct
 *
 * @param[in] el_color_fct A function for the computation of per-element colors,
 *                  e.g. for estimator values.
 *
 * @param[in] el_ud Data-pointer for use by @a el_color_fct
 *
 * @param[in] el_fill_flags Fill-flags needed by @a el_color_fct
 *
 * @param[in] ref_deg Draw the Lagrange-mesh corresponding to @a
 *                  ref_deg, i.e. do @b not refine @a ref_deg times,
 *                  rather @a ref_deg-1 is the number of vertices on
 *                  each edge.
 */
void mesh2off_2d(FILE *gvf,
		 MESH *mesh,
		 VERTEX_COLOR_FCT vertex_color_fct,
		 void *v_ud,
		 FLAGS v_fill_flags,
		 EL_COLOR_FCT el_color_fct,
		 void *el_ud,
		 FLAGS el_fill_flags,
		 int ref_deg,
		 bool flat_shading,
		 bool binary)
{
  DOF_REAL_D_VEC *coords;
  DOF_INT_VEC *idx;
  const FE_SPACE *fe_space;
  const BAS_FCTS *bas_fcts = NULL;
  int i, j, vidx;
  const int (*lagrange_net)[N_VERTICES_2D] = NULL;
  int lagrange_nel = -1;

  TEST_EXIT(sizeof(float) == 4, "FIXME, sizeof(float) != 4.\n");
  TEST_EXIT(sizeof(int) == 4, "FIXME, sizeof(int) != 4.\n");

  /* Appearance block. Also make sure that we have proper light
   * settings.
   */
  fprintf(gvf, "appearance {\n");
  if (flat_shading) {
    fprintf(gvf, "\tshading flat\n");
    if (ref_deg <= 1) {
      fprintf(gvf, "\t+edge\n");
    } else {
      fprintf(gvf, "\t+vect\n");
    }
  } else {
    fprintf(gvf, "\tshading vcflat\n");
    if (ref_deg <= 1) {
      fprintf(gvf, "\t-edge\n");
    } else {
      fprintf(gvf, "\t-vect\n");
    }
  }
  fprintf(gvf,
	  "lighting {\n"
	  "  ambient 0.2 0.2 0.2\n"
	  "  replacelights\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.750000 0.750000 0.750000\n"
	  "    position 0.000000 0.000000 10.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.600000 0.600000 0.600000\n"
	  "    position 0.000000 1.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.400000 0.400000 0.400000\n"
	  "    position 1.000000 -2.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "}\n");
  fprintf(gvf, "}\n"); /* appearance */

  fprintf(gvf, "{ LIST\n");

  if (DIM_OF_WORLD > 3) {
    fprintf(gvf, "{\n%snOFF %d%s\n",
	    vertex_color_fct ? "C" : "",
	    DIM_OF_WORLD,
	    binary ? " BINARY" : "");
  } else {    
    fprintf(gvf, "{\n%sOFF%s\n",
	    vertex_color_fct ? "C" : "",
	    binary ? " BINARY" : "");
  }

  if (mesh->parametric) {
    bool not_all, fast = false;

    not_all = mesh->parametric->not_all;

    if ((coords = get_lagrange_coords(mesh)) != NULL) {

      fe_space = coords->fe_space;
      bas_fcts = fe_space->bas_fcts;

      fast = ref_deg == 1 || ref_deg == bas_fcts->degree;
    }

    if (fast) {
      DOF dofs[bas_fcts->n_bas_fcts];
      const REAL_B *nodes;
      int nv, nel;

      idx = get_dof_int_vec("idx", fe_space);
      FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

      lagrange_net = lagrange_net_2d[ref_deg];
      lagrange_nel = lagrange_nel_2d[ref_deg];      

      /* Be careful: periodic meshes must not use mesh->n_vertices */
      if (ref_deg == 1) {
	if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
	  nv  = mesh->per_n_vertices;
	  nel = mesh->n_elements;
	} else {
	  nv  = mesh->n_vertices;
	  nel = mesh->n_elements;
	}
      } else if (not_all) {
	DOF dofs[bas_fcts->n_bas_fcts];

	nv = 0;
	nel = 0;
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	  bool affine;

	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);

	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
	    if (affine && i >= N_VERTICES_2D) {
	      break;
	    }
	    if (idx->vec[dofs[i]] != -1) {
	      continue;
	    }
	    idx->vec[dofs[i]] = nv++;
	  }

	  if (affine) {
	    nel ++;
	  } else {
	    nel += lagrange_nel;
	  }

	} TRAVERSE_NEXT();
	FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

      } else {
	nv  = coords->fe_space->admin->used_count;
	nel = lagrange_nel * mesh->n_elements;
      }

      if (!binary) {
	fprintf(gvf, "%d %d %d\n", nv, nel, -1);
      } else {
	int triplet[3];
	
	triplet[0] = htonl(nv);
	triplet[1] = htonl(nel);
	triplet[2] = ~0;
	fwrite(triplet, sizeof(triplet), 1, gvf);
      }

      nodes = LAGRANGE_NODES(bas_fcts);

      /* <<< emit vertex coordinates */
      vidx = 0;
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|v_fill_flags) {
	const REAL *rgba;
	GVPOINT gvpt;
	RGBA color;
	bool affine;

	if (not_all || (v_fill_flags & FILL_COORDS)) {
	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	}

	if (!not_all) {
	  affine = ref_deg == 1;
	}

	GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
	  if (affine && i >= N_VERTICES_2D) {
	    break;
	  }
	  if (idx->vec[dofs[i]] != -1) {
	    continue;
	  }
	  idx->vec[dofs[i]] = vidx++;
	  rgba = vertex_color_fct(
	    gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	  fwrite_vertex(gvpt, rgba, binary, gvf);
	}
      } TRAVERSE_NEXT();
      /* >>> */

      /* <<< emit element definitions */
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	RGBA rgba;
	bool affine;

	if (not_all || (el_fill_flags & FILL_COORDS)) {
	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	} else {
	  affine = ref_deg == 1;
	}

	GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);
	
	if (el_color_fct) {
	  el_color_fct(rgba, el_info, el_ud);
	}
	if (affine) {
	  fwrite_triangle(idx->vec[dofs[0]],
			  idx->vec[dofs[1]],
			  idx->vec[dofs[2]],
			  el_color_fct ? rgba : NULL, binary, gvf);
	} else {
	  for (i = 0; i < lagrange_nel; i++) {
	    fwrite_triangle(idx->vec[dofs[lagrange_net[i][0]]],
			    idx->vec[dofs[lagrange_net[i][1]]],
			    idx->vec[dofs[lagrange_net[i][2]]],
			    el_color_fct ? rgba : NULL, binary, gvf);
	  }   
	}
      } TRAVERSE_NEXT();
      /* >>> */

      fprintf(gvf, "}\n"); /* OFF object */

      /* Draw the boundary of the parametric simplexes if necessary. */

      /* <<< emit element edges */
      if (ref_deg > 1) {
	DOF dofs[bas_fcts->n_bas_fcts];
	int nv, ne, n_dof, n_dof_e;

	n_dof_e = ref_deg - 1;

	if (fe_space->admin->flags & ADM_PERIODIC) {
	  ne = mesh->per_n_edges;
	} else {	  
	  ne = mesh->n_edges;
	}

	/* We have standard Lagrange functions in 2D, so the vertices
	 * come first, then the edges, then the center DOFs, if any.
	 */
	n_dof = N_VERTICES_2D + N_EDGES_2D * n_dof_e;

	if (not_all) {

	  FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
	  nv = 0;
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	    bool affine;

	    affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	  
	    GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	    for (i = 0; i < N_VERTICES_2D; i++) {
	      if (idx->vec[dofs[i]] != -1) {
		continue;
	      }
	      idx->vec[dofs[i]] = nv++;
	    }
	    for (; i < n_dof; i++) {
	      if (idx->vec[dofs[i]] >= 0) {
		continue;
	      }
	      if (affine) {
		idx->vec[dofs[i]] = -2;
	      } else {
		idx->vec[dofs[i]] = nv++;
	      }
	    }
	  } TRAVERSE_NEXT();
	
	} else if (fe_space->admin->flags & ADM_PERIODIC) {
	  nv = mesh->per_n_vertices + mesh->per_n_edges * n_dof_e;
	} else {
	  nv = mesh->n_vertices + mesh->n_edges * n_dof_e;
	}

	if (DIM_OF_WORLD > 3) {
	  fprintf(gvf, "{\nCnSKEL %d%s\n",
		  DIM_OF_WORLD, binary ? " BINARY" : "");
	} else {    
	  fprintf(gvf, "{\nCSKEL%s\n", binary ? " BINARY" : "");
	}

	if (!binary) {
	  fprintf(gvf, "%d %d\n", nv, ne);
	} else {
	  int bnvne[2];
	  
	  bnvne[0] = htonl(nv);
	  bnvne[1] = htonl(ne);
	  fwrite(bnvne, sizeof(bnvne), 1, gvf);
	}
	
	FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
    
	vidx = 0;
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|v_fill_flags) {
	  bool affine;
	  const REAL *rgba;
	  RGBA color;
	  GVPOINT gvpt;

	  if (not_all || (v_fill_flags & FILL_COORDS)) {
	    affine =
	      !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = false;
	  }
	  
	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < N_VERTICES_2D; i++) {
	    if (idx->vec[dofs[i]] != -1) {
	      continue;
	    }
	    idx->vec[dofs[i]] = vidx++;

	    rgba = vertex_color_fct(
	      gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	    fwrite_vertex(gvpt, rgba, binary, gvf);
	  }

	  for (; i < n_dof; i++) {
	    if (idx->vec[dofs[i]] >= 0) {
	      continue;
	    }
	    if (affine) {
	      idx->vec[dofs[i]] = -2;
	    } else {
	      idx->vec[dofs[i]] = vidx++;
	      rgba = vertex_color_fct(
		gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	      fwrite_vertex(gvpt, rgba, binary, gvf);
	    }
	  }
	} TRAVERSE_NEXT();

	/* We use idx->vec[edge_dof] to mark the corresponding edge as
	 * handled.
	 */
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_NEIGH) {
	  bool affine;

	  if (not_all) {
	    affine =
	      !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = false;
	  }
	  
	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < N_EDGES_2D; i++) {
	    int v0, v1;
	    const REAL *rgba;

	    if (idx->vec[dofs[N_VERTICES_2D+n_dof_e*i]] == -1) {
	      continue;
	    }
	    v0 = vertex_of_edge(2, i)[0];
	    v1 = vertex_of_edge(2, i)[1];

	    if (i == 2
		|| (el_info->neigh[i] && el_info->opp_vertex[i] == 2)) {
	      rgba = blue;
	    } else {
	      rgba = black;
	    }

	    if (affine) {
	      int edge[N_VERTICES_1D];
		
	      edge[0] = idx->vec[dofs[v0]];
	      edge[1] = idx->vec[dofs[v1]];

	      fwrite_line(N_VERTICES_1D, edge, rgba, binary, gvf);
		
	      for (j = 0; j < n_dof_e; j++) {
		int ldof = N_VERTICES_2D+n_dof_e*i+j;
		idx->vec[dofs[ldof]] = -1; /* mark as done */
	      }
	    } else {
	      int edge[N_VERTICES_1D + n_dof_e];

	      edge[0] = idx->vec[dofs[v0]];
	      for (j = 0; j < n_dof_e; j++) {
		int ldof = N_VERTICES_2D+n_dof_e*i+j;

		edge[1+j] = idx->vec[dofs[ldof]];
		idx->vec[dofs[ldof]] = -1; /* mark as done */
	      }
	      edge[1+j]   = idx->vec[dofs[v1]];
	      fwrite_line(N_VERTICES_1D+n_dof_e, edge, rgba, binary, gvf);
	    }
	  }
	} TRAVERSE_NEXT();
	fprintf(gvf, "}\n"); /* SKEL object */
      }
      /* >>> */
      
      free_dof_int_vec(idx);

    } else { /* slow parametric path */
      ERROR_EXIT("not yet implemented.\n");
    }
  } else { /* non-parametric version */
    int n_dof[N_NODE_TYPES] = { 1, 0, 0, 0 };
    int node;
    int n0;
    int i;

    if (!binary) {
      fprintf(gvf, "%d %d %d\n", mesh->n_vertices, mesh->n_elements, -1);
    } else {
      int triplet[3];

      triplet[0] = htonl(mesh->n_vertices);
      triplet[1] = htonl(mesh->n_elements);
      triplet[2] = ~0;
      fwrite(triplet, sizeof(triplet), 1, gvf);
    }

    fe_space =
      get_dof_space(mesh, "geomview vertex space", n_dof, ADM_FLAGS_DFLT);
    n0 = fe_space->admin->n0_dof[VERTEX];
    node = mesh->node[VERTEX];

    idx = get_dof_int_vec("idx", fe_space);
    FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

    vidx = 0;
    /* <<< emit vertex co-ordinates */
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS|v_fill_flags) {
      const REAL *rgba;
      RGBA color;
      GVPOINT gvpt;

      for (i = 0; i < N_VERTICES_2D; i++) {
	if (idx->vec[el_info->el->dof[node+i][n0]] == -1) {
	  idx->vec[el_info->el->dof[node+i][n0]] = vidx++;
	  rgba = vertex_color_fct(
	    gvpt, color, el_info, el_info->coord[i], vlambda[i], v_ud);
	  fwrite_vertex(gvpt, rgba, binary, gvf);
	}
      }
    } TRAVERSE_NEXT();
    /* >>> */
    
    /* <<< emit element definitions */
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
      RGBA rgba;

      if (el_color_fct) {
	el_color_fct(rgba, el_info, el_ud);
      }
      fwrite_triangle(idx->vec[el_info->el->dof[node+0][n0]],
		      idx->vec[el_info->el->dof[node+1][n0]],
		      idx->vec[el_info->el->dof[node+2][n0]],
		      el_color_fct ? rgba : NULL, binary, gvf);
    } TRAVERSE_NEXT();
    /* >>> */

    fprintf(gvf, "}\n"); /* OFF object */

    free_dof_int_vec(idx);
    free_fe_space((FE_SPACE *)fe_space);
  }

  fprintf(gvf, "}\n"); /* enclosing list object */

  /* Done with this object */
  fflush(gvf);
}

#if 0

#define FACE_DOF(row, col) ((row)*((row)+1)/2+(row)*(row)+(col))
#define CENTER_DOF(height, row, col)					\
  (2*((height)*(height+1)*(height+2))/6					\
   +									\
   (height)*(height)*(height)						\
   +									\
   FACE_DOF(row, col))
#define DEGREE_MAX 4
#define MESH_DIM 3

void print_geomview_tetra(FILE *file,
			  REAL_D *vec,
			  const DOF *dof, int n_bas,
			  int n_e, int n_f, int n_c,
			  float el_color[4], float (*v_color)[4])
{
  int i, j, k, edge, h, r, c;
  int deg = n_e + 1;
  int sorted[DEGREE_MAX+1][DEGREE_MAX+1][DEGREE_MAX+1] = { {{-1,},}, } ;
  int lambda[N_VERTICES_3D];
  int n_faces;

  switch (n_e+1) {
  case 1:
    n_faces = 4;
    break;
  case 2:
    n_faces = 24; /* 2^3 * 3 */
    break;
  case 3:
    n_faces = 72; /* 2^3 * 9 */
    break;
  case 4:
    n_faces = 160; /* 2^5 * 5 */
    break;
  default:
    n_faces = -1;
    break;
  }

  fprintf(file, "\n");
  fprintf(file, "OFF\n%d %d -1\n", n_bas, n_faces);

  if (!v_color) {
    for (i = 0; i < n_bas; i++) {
      fprintf(file, "%e %e %e\n", EXPAND_DOW(vec[dof[i]]));
    }
  } else {
    for (i = 0; i < n_bas; i++) {
      fprintf(file, "%e %e %e %f %f %f %f\n",
	      EXPAND_DOW(vec[dof[i]]),
	      v_color[i][0],
	      v_color[i][1],
	      v_color[i][2],
	      v_color[i][3]);
    }
  }

  for (lambda[3] = 0; lambda[3] < deg+1; lambda[3]++) { /* z */
    for (lambda[2] = 0; lambda[2] < deg+1 - lambda[3]; lambda[2]++) { /* y */
      for (lambda[1] = 0;
	   lambda[1] < deg+1 - lambda[2] - lambda[3]; lambda[1]++) { /* x */
	int n_z, zero[MESH_DIM], n_n_z, n_zero[MESH_DIM];

	lambda[0] = deg - lambda[1] - lambda[2] - lambda[3];

	n_z = 0;
	n_n_z = 0;	
	for (i = 0; i < N_VERTICES_3D; i++) {
	  if (lambda[i] == 0) {
	    zero[n_z++] = i;
	  } else {
	    n_zero[n_n_z++] = i;
	  }
	}

	i = lambda[3];
	j = lambda[2];
	k = lambda[1];

	switch (n_z) {
	case 3: /* a vertex */
	  sorted[i][j][k] = n_zero[0];
	  break;
	case 2: /* an edge */
	  /* determine the number */
	  edge = edge_of_vertices_3d[n_zero[0]][n_zero[1]];
	  sorted[i][j][k] = N_VERTICES_3D + edge*n_e + lambda[n_zero[1]]-1;
	  break;
	case 1: /* a face */
	  sorted[i][j][k] = N_VERTICES_3D + N_EDGES_3D*n_e + zero[0]*n_f;
	  sorted[i][j][k] += FACE_DOF(lambda[n_zero[2]]-1, lambda[n_zero[1]]-1);
	  break;
	case 0: /* the center */
	  sorted[i][j][k] =
	    N_VERTICES_3D + N_EDGES_3D*n_e + N_FACES_3D*n_f;
	  sorted[i][j][k] +=
	    CENTER_DOF(lambda[n_zero[3]]-1,
		       lambda[n_zero[2]]-1,
		       lambda[n_zero[1]]-1);
	  break;
	}
      }
    }
  }
  
  /* output of the faces */
  for (h = 0; h < deg; h++) {
    for (r = 0; r < deg-h; r++) {
      for (c = 0; c < deg-r-h; c++) {
	int triangles[][N_VERTICES_2D][MESH_DIM] = {
	  /* always present */
	  { { h  , r  , c   }, { h  , r  , c+1 }, { h  , r+1, c   } },
	  { { h  , r  , c   }, { h  , r  , c+1 }, { h+1, r  , c   } },
	  { { h  , r  , c   }, { h  , r+1, c   }, { h+1, r  , c   } },
	  { { h+1, r  , c   }, { h  , r+1, c   }, { h  , r  , c+1 } },

	  /* only if c < deg-h-r-1 */
	  { { h  , r  , c+1 }, { h+1, r  , c+1 }, { h+1, r  , c   } },
	  { { h  , r  , c+1 }, { h+1, r  , c+1 }, { h  , r+1, c   } },
	  { { h  , r+1, c   }, { h+1, r  , c   }, { h+1, r  , c+1 } },

	  { { h  , r+1, c   }, { h+1, r  , c   }, { h+1, r+1, c   } },
	  { { h  , r+1, c   }, { h+1, r  , c+1 }, { h+1, r+1, c   } },

	  { { h  , r+1, c+1 }, { h  , r  , c+1 }, { h  , r+1, c   } },
	  { { h  , r+1, c+1 }, { h  , r+1, c   }, { h+1, r  , c+1 } },

	  /* only if c < deg-h-r-2 */
	  { { h  , r+1, c+1 }, { h+1, r  , c+1 }, { h+1, r+1, c   } },

	};
	
	for (i = 0; i < 4; i++) {
	  fprintf(
	    file, "3 %d %d %d",
	    sorted[
	      triangles[i][0][0]][triangles[i][0][1]][triangles[i][0][2]],
	    sorted[
	      triangles[i][1][0]][triangles[i][1][1]][triangles[i][1][2]],
	    sorted[
	      triangles[i][2][0]][triangles[i][2][1]][triangles[i][2][2]]);
	  if (el_color) {
	    fprintf(file, " %f %f %f %f",
		    el_color[0], el_color[1], el_color[2], el_color[3]);
	  } else if (!v_color) {
	    fprintf(file, " 0.5 0.5 0.0 1.0");
	  }
	  fprintf(file, "\n");
	}
	
	if (c < deg-r-h-1) {
	  for (; i < 12; i++) {
	    fprintf(
	      file, "3 %d %d %d",
	      sorted[
		triangles[i][0][0]][triangles[i][0][1]][triangles[i][0][2]],
	      sorted[
		triangles[i][1][0]][triangles[i][1][1]][triangles[i][1][2]],
	      sorted[
		triangles[i][2][0]][triangles[i][2][1]][triangles[i][2][2]]);
	    if (el_color) {
	      fprintf(file, " %f %f %f %f",
		      el_color[0], el_color[1], el_color[2], el_color[3]);
	    } else if (!v_color) {
	      fprintf(file, " 0.5 0.5 0.0 1.0");	  
	    }
	    fprintf(file, "\n");
	  }
	}
      }
    }
  }
}
#endif

#undef MESH_DIM

/**Generate Geomview OOGL for the given mesh, using colors to display
 * data. This is the 3d version; DIM_OF_WORLD is arbitrary.
 *
 * @param[in] gvf File descriptor for the conversation with Geomview.
 *
 * @param[in] mesh To underlying ALBERTA mesh.
 *
 * @param[in] vertex_color_fct A function for the computation of per-vertex
 *                 colors.
 *
 * @param[in] v_ud User-data for @a vertex_color_fct.
 *
 * @param[in] v_fill_flags Fill-flags needed by @a vertex_color_fct
 *
 * @param[in] el_color_fct A function for the computation of per-element colors,
 *                  e.g. for estimator values.
 *
 * @param[in] el_ud Data-pointer for use by @a el_color_fct
 *
 * @param[in] el_fill_flags Fill-flags needed by @a el_color_fct
 *
 * @param[in] ref_deg Draw the Lagrange-mesh corresponding to @a
 *                  ref_deg, i.e. do @b not refine @a ref_deg times,
 *                  rather @a ref_deg-1 is the number of vertices on
 *                  each edge.
 */
void mesh2off_3d(FILE *gvf,
		 MESH *mesh,
		 VERTEX_COLOR_FCT vertex_color_fct,
		 void *v_ud,
		 FLAGS v_fill_flags,
		 EL_COLOR_FCT el_color_fct,
		 void *el_ud,
		 FLAGS el_fill_flags,
		 int ref_deg,
		 bool flat_shading,
		 bool binary)
{
  static const int (*face_list[DEG_MAX+1])[N_VERTICES_2D];
  const int (*flist)[N_VERTICES_2D] = NULL;
  DOF_REAL_D_VEC *coords = NULL;
  DOF_UCHAR_VEC *face_plotted = NULL;
  const FE_SPACE *face_fe_space = NULL;
  int n0_f;
  int node_f;
  DOF_INT_VEC *idx;
  const FE_SPACE *fe_space;
  RGBA rgba; 
  int i, j, vidx;

  TEST_EXIT(sizeof(float) == 4, "FIXME, sizeof(float) != 4.\n");
  TEST_EXIT(sizeof(int) == 4, "FIXME, sizeof(int) != 4.\n");

  fprintf(gvf, "appearance {\n");
  if (flat_shading) {
    fprintf(gvf, "\tshading flat\n");
    if (ref_deg == 1) {
      fprintf(gvf, "\t+edge\n");
    } else {
      fprintf(gvf, "\t+vect\n");
    }
  } else {
    fprintf(gvf, "\tshading vcflat\n");
    if (ref_deg == 1) {
      fprintf(gvf, "\t-edge\n");
    } else {
      fprintf(gvf, "\t-vect\n");
    }
  }
  fprintf(gvf,
	  "lighting {\n"
	  "  ambient 0.2 0.2 0.2\n"
	  "  replacelights\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.750000 0.750000 0.750000\n"
	  "    position 0.000000 0.000000 10.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.600000 0.600000 0.600000\n"
	  "    position 0.000000 1.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "  light {\n"
	  "    ambient 0.000000 0.000000 0.000000\n"
	  "    color 0.400000 0.400000 0.400000\n"
	  "    position 1.000000 -2.000000 -1.000000 0.000000\n"
	  "    location camera\n"
	  "  }\n"
	  "}\n");
  fprintf(gvf, "}\n"); /* appearance */

  fprintf(gvf, "{ LIST\n");

  if (DIM_OF_WORLD > 3) {
    fprintf(gvf, "{\nCnOFF %d%s\n", DIM_OF_WORLD, binary ? " BINARY" : "");
  } else {    
    fprintf(gvf, "{\nCOFF%s\n", binary ? " BINARY" : "");
  }

  if (mesh->parametric) {
    /* <<< parametric version */
    const BAS_FCTS *bas_fcts;
    bool not_all, fast = false;

    not_all = mesh->parametric->not_all;

    if ((coords = get_lagrange_coords(mesh)) != NULL) {

      fe_space = coords->fe_space;
      bas_fcts = fe_space->bas_fcts;

      fast = ref_deg == 1 || ref_deg == bas_fcts->degree;
    } else {
      return;
    }

    if (fast) {
      DOF dofs[bas_fcts->n_bas_fcts];
      const REAL_B *nodes = LAGRANGE_NODES(bas_fcts);
      int n_el_faces, n_face_faces;
      int nv, nf;

      if (bas_fcts->degree >= 3) {
	face_plotted = get_dof_uchar_vec("EDGE/FACE flags", fe_space);
	n0_f = fe_space->admin->n0_dof[FACE];
	node_f = mesh->node[FACE];
      } else {
	int n_dof[N_NODE_TYPES] = { 0, 0, 0, 1 };
	FLAGS adm_flags = ADM_FLAGS_DFLT;

	/* we need a DOF_ADMIN for the faces, there is no way around
	 * that.
	 */
	if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
	  adm_flags |= ADM_PERIODIC;
	}
	face_fe_space = get_dof_space(mesh, "face FE_SPACE", n_dof, adm_flags);
	n0_f = face_fe_space->admin->n0_dof[FACE];
	node_f = mesh->node[FACE];
	face_plotted = get_dof_uchar_vec("FACE flags", face_fe_space);
      }
      
      if (face_list[ref_deg] == NULL) {
	face_list[ref_deg] = generate_face_list(ref_deg);
      }
      flist = face_list[ref_deg];

      idx = get_dof_int_vec("idx", fe_space);
      FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

      n_face_faces = lagrange_nel_2d[ref_deg];
      n_el_faces = (4*lagrange_nel_3d[ref_deg] - 4*n_face_faces) / 2;

      if (ref_deg == 1) {
	if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
	  nv = mesh->per_n_vertices;
	  nf = mesh->per_n_faces;
	} else {
	  nv = mesh->n_vertices;
	  nf = mesh->n_faces;
	}
	FOR_ALL_DOFS(face_plotted->fe_space->admin, face_plotted->vec[dof] = 1);
      } else if (not_all) {

	FOR_ALL_DOFS(face_plotted->fe_space->admin, face_plotted->vec[dof] = 0);
	
	nv = 0;
	nf = 0;
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	  bool affine;

	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);

	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  if (affine) {
	    for (i = 0; i < N_FACES_3D; i++) {
	      DOF dof_f = el_info->el->dof[node_f+i][n0_f];
	      
	      if (face_plotted->vec[dof_f] == 0) {
		++nf; /* one more face */
		face_plotted->vec[dof_f] = 1;
	      }
	    }
	    for (i = 0; i < N_VERTICES_3D; i++) {
	      if (idx->vec[dofs[i]] != -1) {
		continue;
	      }
	      idx->vec[dofs[i]] = nv++;
	    }
	  } else {
	    for (i = 0; i < N_FACES_3D; i++) {
	      DOF dof_f = el_info->el->dof[node_f+i][n0_f];
	      
	      if (face_plotted->vec[dof_f] == 1) {
		--nf;
		face_plotted->vec[dof_f] = 0;
 	      }

	      if (face_plotted->vec[dof_f] == 0) {
		nf += n_face_faces;
		face_plotted->vec[dof_f] = 2;
	      }
	    }
	    nf += n_el_faces;
	    for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
	      if (idx->vec[dofs[i]] != -1) {
		continue;
	      }
	      idx->vec[dofs[i]] = nv++;
	    }
	  }
	} TRAVERSE_NEXT();
	FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
      } else {
	nv = coords->fe_space->admin->used_count;
	if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
	  nf = mesh->per_n_faces;
	} else {
	  nf = mesh->n_faces;
	}
	nf *= n_face_faces;
	nf += n_el_faces * mesh->n_elements;
	FOR_ALL_DOFS(face_plotted->fe_space->admin, face_plotted->vec[dof] = 2);
      }

      if (!binary) {
	fprintf(gvf, "%d %d %d\n", nv, nf, -1);
      } else {
	int triplet[3];
	
	triplet[0] = htonl(nv);
	triplet[1] = htonl(nf);
	triplet[2] = ~0;
	fwrite(triplet, sizeof(triplet), 1, gvf);
      }
      
      /* <<< emit vertex coordinates */
      vidx = 0;
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|v_fill_flags) {
	RGBA color;
	const REAL *rgba;
	GVPOINT gvpt;
	bool affine;

	if (not_all || (v_fill_flags & FILL_COORDS)) {
	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	} else {
	  affine = ref_deg == 1;
	}

	GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	for (i = 0; i < bas_fcts->n_bas_fcts; i++) {

	  if (affine && i >= N_VERTICES_3D) {
	    break;
	  }
	  if (idx->vec[dofs[i]] != -1) {
	    continue;
	  }

	  idx->vec[dofs[i]] = vidx++;
	  rgba = vertex_color_fct(
	    gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	  fwrite_vertex(gvpt, rgba, binary, gvf);
	}

      } TRAVERSE_NEXT();
      /* >>> */

      /* <<< emit element definitions */
      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	bool affine;

	if (not_all || (el_fill_flags & FILL_COORDS)) {
	  affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	} else {
	  affine = ref_deg == 1;
	}
	
	GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	if (el_color_fct) {
	  el_color_fct(rgba, el_info, el_ud);
	}
	
	if (affine) {
	  for (i = 0; i < N_FACES_3D; i++) {
	    DOF dof_f = el_info->el->dof[node_f+i][n0_f];

	    if (face_plotted->vec[dof_f] == 1) {
	      fwrite_triangle(idx->vec[dofs[(i+1)%N_VERTICES_3D]],
			      idx->vec[dofs[(i+2)%N_VERTICES_3D]],
			      idx->vec[dofs[(i+3)%N_VERTICES_3D]],
			      el_color_fct ? rgba : NULL, binary, gvf);
	      face_plotted->vec[dof_f] = 0; /* plot only once */
	    }
	  }
	} else {

	  /* first plot the sub-faces on each "real" face */
	  for (i = 0; i < N_FACES_3D; i++) {
	    DOF dof_f = el_info->el->dof[node_f+i][n0_f];

	    if (face_plotted->vec[dof_f] == 2) {
	      for (j = 0; j < n_face_faces; j++) {
		const int *face = flist[i*n_face_faces+j];
		fwrite_triangle(idx->vec[dofs[face[0]]],
				idx->vec[dofs[face[1]]],
				idx->vec[dofs[face[2]]],
				el_color_fct ? rgba : NULL, binary, gvf);
	      }
	      face_plotted->vec[dof_f] = 0; /* plot only once */
	    }
	  }
	  /* now plot all the interior faces */
	  for (i = 0; i < n_el_faces; i++) {
	    const int *face = flist[4*n_face_faces+i];

	    fwrite_triangle(idx->vec[dofs[face[0]]],
			    idx->vec[dofs[face[1]]],
			    idx->vec[dofs[face[2]]],
			    el_color_fct ? rgba : NULL, binary, gvf);
	  }
	}
      } TRAVERSE_NEXT();

      /* >>> */
      
      fprintf(gvf, "}\n"); /* OFF object */

      /* Draw the boundary of the parametric simplexes if necessary. */

      if (ref_deg > 1) {
	int nv, ne, n_dof, n_dof_e;
	
	n_dof_e = ref_deg - 1; /* extra DOFs on edges */

	if (fe_space->admin->flags & ADM_PERIODIC) {
	  ne = mesh->per_n_edges;
	} else {	  
	  ne = mesh->n_edges;
	}

	/* We have standard Lagrange functions, so the vertices come
	 * first, then the edges, then the faces, then the center
	 * DOFs, if any.
	 */
	n_dof = N_VERTICES_3D + N_EDGES_3D * (ref_deg-1);

	if (not_all) {

	  FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
	  nv = 0;
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	    bool affine;

	    affine = !mesh->parametric->init_element(el_info, mesh->parametric);
	  
	    GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	    for (i = 0; i < N_VERTICES_3D; i++) {
	      if (idx->vec[dofs[i]] != -1) {
		continue;
	      }
	      idx->vec[dofs[i]] = nv++;
	    }
	    for (; i < n_dof; i++) {
	      if (idx->vec[dofs[i]] != -1) {
		continue;
	      }
	      if (affine) {
		idx->vec[dofs[i]] = -2;
	      } else {
		idx->vec[dofs[i]] = nv++;
	      }
	    }
	  } TRAVERSE_NEXT();
	
	} else if (fe_space->admin->flags & ADM_PERIODIC) {
	  nv = mesh->per_n_vertices + mesh->per_n_edges * n_dof_e;
	} else {
	  nv = mesh->n_vertices + mesh->n_edges * n_dof_e;
	}

	if (DIM_OF_WORLD > 3) {
	  fprintf(gvf, "{\nCnSKEL %d%s\n",
		  DIM_OF_WORLD, binary ? " BINARY" : "");
	} else {    
	  fprintf(gvf, "{\nCSKEL%s\n", binary ? " BINARY" : "");
	}

	if (!binary) {
	  fprintf(gvf, "%d %d\n", nv, ne);
	} else {
	  int bnvne[2];
	  
	  bnvne[0] = htonl(nv);
	  bnvne[1] = htonl(ne);
	  fwrite(bnvne, sizeof(bnvne), 1, gvf);
	}
	
	FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);
    
	vidx = 0;
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|v_fill_flags) {
	  bool affine;
	  RGBA color;
	  GVPOINT gvpt;
	  const REAL *rgba;

	  if (not_all || (v_fill_flags & FILL_COORDS)) {
	    affine =
	      !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = false;
	  }
	  
	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < N_VERTICES_3D; i++) {
	    if (idx->vec[dofs[i]] != -1) {
	      continue;
	    }
	    idx->vec[dofs[i]] = vidx++;
	    rgba = vertex_color_fct(
	      gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	    fwrite_vertex(gvpt, rgba, binary, gvf);
	  }

	  for (; i < n_dof; i++) {
	    if (idx->vec[dofs[i]] != -1) {
	      continue;
	    }
	    if (affine) {
	      idx->vec[dofs[i]] = 0;
	    } else {
	      idx->vec[dofs[i]] = vidx++;
	      rgba = vertex_color_fct(
		gvpt, color, el_info, coords->vec[dofs[i]], nodes[i], v_ud);
	      fwrite_vertex(gvpt, rgba, binary, gvf);
	    }
	  }

	  /* Mark refinement edge (no. 0) for blue coloring */
	  if (idx->vec[dofs[N_VERTICES_3D]] >= 0) {
	    idx->vec[dofs[N_VERTICES_3D]] = -idx->vec[dofs[N_VERTICES_3D]]-2;
	  }

	} TRAVERSE_NEXT();

	/* We use idx->vec[edge_dof] to mark the corresponding edge as
	 * handled.
	 */
	TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_NEIGH) {
	  int affine, ref_edge;

	  if (not_all) {
	    affine =
	      !mesh->parametric->init_element(el_info, mesh->parametric);
	  } else {
	    affine = false;
	  }
	  
	  GET_DOF_INDICES(bas_fcts, el_info->el, fe_space->admin, dofs);

	  for (i = 0; i < N_EDGES_3D; i++) {
	    int v0, v1;
	    const REAL *rgba;

	    if (idx->vec[dofs[N_VERTICES_3D+n_dof_e*i]] == -1) {
	      continue;
	    }

	    v0 = vertex_of_edge(3, i)[0];
	    v1 = vertex_of_edge(3, i)[1];

	    /* FIXME: I think the decision about the ref-edge may be
	     * completely broken. I just have no time for this
	     * cosmetic stuff ATM.
	     */
	    if (idx->vec[dofs[N_VERTICES_3D+n_dof_e*i]] <= -2) {
	      ref_edge = true;
	      idx->vec[dofs[N_VERTICES_3D+n_dof_e*i]] =
		-idx->vec[dofs[N_VERTICES_3D+n_dof_e*i]]-2;
	    } else {
	      ref_edge = false;
	    }

	    rgba = ref_edge ? blue : black;

	    if (affine) {
	      int edge[N_VERTICES_1D];
		
	      edge[0] = idx->vec[dofs[v0]];
	      edge[1] = idx->vec[dofs[v1]];

	      fwrite_line(N_VERTICES_1D, edge, rgba, binary, gvf);
		
	      for (j = 0; j < n_dof_e; j++) {
		int ldof = N_VERTICES_3D+n_dof_e*i+j;
		idx->vec[dofs[ldof]] = -1; /* mark as done */
	      }
	    } else {
	      int edge[N_VERTICES_1D + n_dof_e];

	      edge[0] = idx->vec[dofs[v0]];
	      for (j = 0; j < n_dof_e; j++) {
		int ldof = N_VERTICES_3D+n_dof_e*i+j;

		if (idx->vec[dofs[ldof]] <= -2) {
		  idx->vec[dofs[ldof]] = -idx->vec[dofs[ldof]]-2;
		}

		edge[1+j] = idx->vec[dofs[ldof]];
		idx->vec[dofs[ldof]] = -1; /* mark as done */
	      }
	      edge[1+j]   = idx->vec[dofs[v1]];
	      fwrite_line(N_VERTICES_1D+n_dof_e, edge, rgba, binary, gvf);
	    }
	  }
	} TRAVERSE_NEXT();
	fprintf(gvf, "}\n"); /* SKEL object */
      }

    } else { /* slow parametric path */
      ERROR_EXIT("not yet implemented.\n");
    }

    /* >>> */
  } else { /* non-parametric version */
    /* <<< non-parametric version */
    int n_el_faces, n_face_faces;
    int nv, nf, n0;
    const int f_n_dof[N_NODE_TYPES] = { 0, 0, 0, 1 };
    const int v_n_dof[N_NODE_TYPES] = { 1, 0, 0, 0 };
    int node;
    FLAGS adm_flags = ADM_FLAGS_DFLT;

    /* we need a DOF_ADMIN for the faces, there is no way around
     * that.
     */
    if (mesh->is_periodic) {
      adm_flags |= ADM_PERIODIC;
    }
    face_fe_space = get_dof_space(mesh, "face FE_SPACE", f_n_dof, adm_flags);
    fe_space = get_dof_space(mesh, "geomview vertex space", v_n_dof, adm_flags);

    n0_f = face_fe_space->admin->n0_dof[FACE];
    node_f = mesh->node[FACE];
    face_plotted = get_dof_uchar_vec("FACE flags", face_fe_space);
      
    if (face_list[1] == NULL) {
      face_list[1] = generate_face_list(1);
    }
    flist = face_list[1];

    n0 = fe_space->admin->n0_dof[VERTEX];
    node = mesh->node[VERTEX];

    idx = get_dof_int_vec("idx", fe_space);
    FOR_ALL_DOFS(fe_space->admin, idx->vec[dof] = -1);

    n_face_faces = lagrange_nel_2d[1];
    n_el_faces = (4*lagrange_nel_3d[1] - 4*n_face_faces) / 2;

    if (mesh->is_periodic && (fe_space->admin->flags & ADM_PERIODIC)) {
      nv = mesh->per_n_vertices;
      nf = mesh->per_n_faces;
    } else {
      nv = mesh->n_vertices;
      nf = mesh->n_faces;
    }
    FOR_ALL_DOFS(face_plotted->fe_space->admin, face_plotted->vec[dof] = 1);
    
    if (!binary) {
      fprintf(gvf, "%d %d %d\n", nv, nf, -1);
    } else {
      int triplet[3];
	
      triplet[0] = htonl(nv);
      triplet[1] = htonl(nf);
      triplet[2] = ~0;
      fwrite(triplet, sizeof(triplet), 1, gvf);
    }
      
    /* <<< emit vertex coordinates */
    vidx = 0;
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS|v_fill_flags) {
      RGBA color;
      const REAL *rgba;
      GVPOINT gvpt;

      for (i = 0; i < N_VERTICES_3D; i++) {
	DOF dof = el_info->el->dof[node+i][n0];

	if (idx->vec[dof] != -1) {
	  continue;
	}
	idx->vec[dof] = vidx++;
	rgba = vertex_color_fct(
	  gvpt, color, el_info, el_info->coord[i], vlambda[i], v_ud);
	fwrite_vertex(gvpt, rgba, binary, gvf);
      }
    } TRAVERSE_NEXT();
    /* >>> */

    /* <<< emit element definitions */
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|el_fill_flags) {
	
      if (el_color_fct) {
	el_color_fct(rgba, el_info, el_ud);
      }
	
      for (i = 0; i < N_FACES_3D; i++) {
	DOF dof_f = el_info->el->dof[node_f+i][n0_f];
	DOF **dof = el_info->el->dof;

	if (face_plotted->vec[dof_f] == 1) {
	  fwrite_triangle(idx->vec[dof[node+(i+1)%N_VERTICES_3D][n0]],
			  idx->vec[dof[node+(i+2)%N_VERTICES_3D][n0]],
			  idx->vec[dof[node+(i+3)%N_VERTICES_3D][n0]],
			  el_color_fct ? rgba : NULL, binary, gvf);
	  face_plotted->vec[dof_f] = 0; /* plot only once */
	}
      }
    } TRAVERSE_NEXT();
    /* >>> */
    
    fprintf(gvf, "}\n"); /* OFF object */

    free_dof_int_vec(idx);
    free_fe_space((FE_SPACE *)face_fe_space);
    free_fe_space((FE_SPACE *)fe_space);
    /* >>> */
  }
  
  fprintf(gvf, "}\n"); /* enclosing list object */

  /* Done with this object */
  fflush(gvf);
}

/**Make sure there is a camera looking at the given object, previously
 * defined by a "(read geometry { define ... })" statement.  This
 * function creates a new OOGL geometry with name @a geom_name from
 * the geometry handle @a object. The object is translated to the
 * position given by { i*diam[0], j*diam[1], k*diam[2] } using an INST
 * geometry.
 */
static void lookat(FILE *gvf, int dim, double *diam, const int *quadrant,
		   const char *geom_name, const char *object)
{
  if (dim <= 3) {
    fprintf(gvf,
	    "(geometry  \"%s\" { INST\n"
	    "\tgeom { : \"%s\" }\n"
	    "\ttransform {\n",
	    geom_name, object);
    fprintf(gvf,
	    "\t\t1 0 0 0\n"
	    "\t\t0 1 0 0\n"
	    "\t\t0 0 1 0\n"
	    "\t\t%e %e %e 1\n",
	    (REAL)quadrant[0]*diam[0],
	    (REAL)quadrant[1]*diam[1],
	    (REAL)quadrant[2]*diam[2]);
    fprintf(gvf, "\t}\n})\n");
  } else {
    int i, j;
    
    /* Should we create a new cluster looking at the object? */
    fprintf(gvf, "(dimension %d)\n", dim);
    fprintf(gvf,
	    "(geometry  \"%s\" { INST\n"
	    "\tgeom { : \"%s\" }\n"
	    "\tntransform {\n"
	    "\t\t%d %d\n",
	    geom_name, object, dim+1, dim+1);
    fprintf(gvf, "\t\t1");
    for (j = 0; j < dim; j++) {
      fprintf(gvf, " %e", (REAL)quadrant[j]*diam[j]);
    }
    fprintf(gvf, "\n");
    for (i = 1; i <= dim; i++) {
      fprintf(gvf, "\t\t");
      for (j = 0; j <= dim ; j++) {
	fprintf(gvf, "%d ", i == j);
      }
      fprintf(gvf, "\n");
    }
    fprintf(gvf, "\t}\n})\n");
  }
  fflush(gvf);
}

struct est_data 
{
  REAL (*get_est)(EL *el);
  REAL min, max, range;
};
  
static void est_color_fct(RGBA rgba,
			  const EL_INFO *el_info,
			  void *vest_data)
{
  struct est_data *est_data = vest_data;
  REAL est;
  
  est = est_data->get_est(el_info->el);

  est = (est - est_data->min) / est_data->range;

  if (est != est)
    est = 0.0;

  rgba[0] = est;
  rgba[1] = 4.0*est*(1.0 - est);
  rgba[2] = 1.0 - est;
  rgba[3] = 1.0;
}

static const REAL *vertex_nocolor_fct(GVPOINT gvpt,
				      RGBA color,
				      const EL_INFO *el_info,
				      const REAL_D world,
				      const REAL_B lambda,
				      void *ud)
{
 int i;

  COPY_DOW(world, gvpt);
  for (i = DIM_OF_WORLD; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }
  return white;
}

struct uh_data
{
  const DOF_REAL_VEC *uh;
  const BAS_FCTS *bas_fcts;
  EL_REAL_VEC *uh_loc;
  REAL min, max, range;
};

/* Generate a function graph if we have spare dimensions available. */
static const REAL *uh_vertex_color_fct(GVPOINT gvpt,
				       RGBA color,
				       const EL_INFO *el_info,
				       const REAL_D world,
				       const REAL_B lambda,
				       void *ud)
{
  struct uh_data *uh_data = ud;
  REAL value;
  int i;

  fill_el_real_vec(uh_data->uh_loc, el_info->el, uh_data->uh);
  value = eval_uh(lambda, uh_data->uh_loc, uh_data->bas_fcts);
  
  value = (value - uh_data->min) / uh_data->range;
  
  color[0] = value;
  color[1] = 4.0*value*(1.0 - value);
  color[2] = 1.0 - value;
  color[3] = 1.0;

  COPY_DOW(world, gvpt);
  if (DIM_OF_WORLD < GVDIM) {
    gvpt[DIM_OF_WORLD] = value;
  }
  for (i = DIM_OF_WORLD+1; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }

  return color;
}

/* Just coloring, but no function graph. */
static const REAL *uh_nograph_vertex_color_fct(GVPOINT gvpt,
					       RGBA color,
					       const EL_INFO *el_info,
					       const REAL_D world,
					       const REAL_B lambda,
					       void *ud)
{
  struct uh_data *uh_data = ud;
  REAL value;
  int i;

  fill_el_real_vec(uh_data->uh_loc, el_info->el, uh_data->uh);
  value = eval_uh(lambda, uh_data->uh_loc, uh_data->bas_fcts);
  
  value = (value - uh_data->min) / uh_data->range;
  
  color[0] = value;
  color[1] = 4.0*value*(1.0 - value);
  color[2] = 1.0 - value;
  color[3] = 1.0;

  COPY_DOW(world, gvpt);
  for (i = DIM_OF_WORLD; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }

  return color;
}

struct u_data
{
  REAL (*u_loc)(const EL_INFO *el_info,
		const REAL_B lambda,
		void *ud);
  void *ud;
  REAL min, max, range;
};

/* Generate a function graph if we have spare dimensions available */
static const REAL *u_vertex_color_fct(GVPOINT gvpt,
				      RGBA rgba,
				      const EL_INFO *el_info,
				      const REAL_D world,
				      const REAL_B lambda,
				      void *ud)
{  
  struct u_data *u_data = ud;
  REAL value;
  int i;

  value = u_data->u_loc(el_info, lambda, u_data->ud);
  
  value = (value - u_data->min) / u_data->range;
  
  rgba[0] = value;
  rgba[1] = 4.0*value*(1.0 - value);
  rgba[2] = 1.0 - value;
  rgba[3] = 1.0;

  COPY_DOW(world, gvpt);
  if (DIM_OF_WORLD < GVDIM) {
    gvpt[DIM_OF_WORLD] = value;
  }
  for (i = DIM_OF_WORLD+1; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }

  return rgba;
}

struct err_data
{
  REAL  (*u_loc)(const EL_INFO *el_info,
		 const REAL_B lambda,
		 void *ud);
  void  *ud;
  const DOF_REAL_VEC *uh;
  const BAS_FCTS *bas_fcts;
  EL_REAL_VEC *uh_loc;
  REAL  min, max, range;
};

/* Generate a function graph, if we have spare dimensions available. */
static const REAL *err_vertex_color_fct(GVPOINT gvpt,
					RGBA rgba,
					const EL_INFO *el_info,
					const REAL_D world,
					const REAL_B lambda,
					void *ud)
{
  struct err_data *err_data = ud;
  REAL value;
  int i;

  value = err_data->u_loc(el_info, lambda, err_data->ud);
  fill_el_real_vec(err_data->uh_loc, el_info->el, err_data->uh);
  value -= eval_uh(lambda, err_data->uh_loc, err_data->bas_fcts);
  
  value = (fabs(value) - err_data->min) / err_data->range;
  
  rgba[0] = value;
  rgba[1] = 4.0*value*(1.0 - value);
  rgba[2] = 1.0 - value;
  rgba[3] = 1.0;

  COPY_DOW(world, gvpt);
  if (DIM_OF_WORLD < GVDIM) {
    gvpt[DIM_OF_WORLD] = value;
  }
  for (i = DIM_OF_WORLD+1; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }

  return rgba;
}

#if 0
/* Do not generate a function graph, even if we have spare dimensions
 * available.
 */
static const REAL *err_nograph_vertex_color_fct(GVPOINT gvpt,
						RGBA rgba,
						const EL_INFO *el_info,
						const REAL_D world,
						const REAL_B lambda,
						void *ud)
{
  struct err_data *err_data = ud;
  REAL value;
  int i;

  value = err_data->u_loc(el_info, lambda, err_data->ud);
  err_data->bas_fcts->get_real_vec(el_info->el, err_data->uh, err_data->uh_loc);
  value -= eval_uh(lambda, err_data->uh_loc, err_data->bas_fcts);
  
  value = (fabs(value) - err_data->min) / err_data->range;
  
  rgba[0] = value;
  rgba[1] = 4.0*value*(1.0 - value);
  rgba[2] = 1.0 - value;
  rgba[3] = 1.0;

  COPY_DOW(world, gvpt);
  for (i = DIM_OF_WORLD; i < GVDIM; i++) {
    gvpt[i] = 0.0;
  }

  return rgba;
}
#endif

void togeomview(MESH *mesh,
		const DOF_REAL_VEC *u_h,
		REAL uh_min, REAL uh_max,
		REAL (*get_est)(EL *el),
		REAL est_min, REAL est_max,
		REAL (*u_loc)(const EL_INFO *el_info,
			      const REAL_B lambda,
			      void *ud),
		void *ud, FLAGS fill_flags,
		REAL u_min, REAL u_max)
{
  FUNCNAME("togeomview");
  const PARAMETRIC *parametric;
  mesh2off_func mesh2off;
  FILE *gvf;
  int domesh;
  const REAL_B *nodes = vlambda;
  int ref_deg = 1, n_nodes;
  DOF_REAL_D_VEC *coords;
  struct est_data est_data;
  EL_COLOR_FCT el_color_fct = NULL;
  struct uh_data uh_data;
  EL_REAL_VEC *uh_loc = NULL;
  VERTEX_COLOR_FCT uh_nograph_fct = vertex_nocolor_fct;

  domesh = mesh || get_est;

  if (!mesh) {
    if (u_h) {
      mesh = u_h->fe_space->mesh;
    } else {
      WARNING("No mesh given.\n");
      return;
    }
  }

  switch (mesh->dim) {
  case 1: mesh2off = mesh2off_1d; break;
  case 2: mesh2off = mesh2off_2d; break;
  case 3: mesh2off = mesh2off_3d; break;
  default: ERROR_EXIT("Unsupported mesh->dim: %d.\n", mesh->dim); return;
  }

  n_nodes = N_VERTICES(mesh->dim);
  dof_compress(mesh);

  if ((parametric = mesh->parametric) != NULL &&
      ((coords = get_lagrange_coords(mesh)) != NULL)) {
    ref_deg = coords->fe_space->bas_fcts->degree;
    nodes = LAGRANGE_NODES(coords->fe_space->bas_fcts);
    n_nodes = coords->fe_space->bas_fcts->n_bas_fcts;
  }

#if DEBUG_TOGEOMVIEW
  if (global_gvf == NULL) {
    global_gvf = popen("tee /tmp/blah|togeomview -c", "w");
    TEST_EXIT(global_gvf != NULL, "Error talking to Geomview.\n");
    make_logo(global_gvf);
  }
#else
  if (gv_pid != -1) {
    pid_t cpid;
    
    cpid = waitpid(gv_pid, NULL, WNOHANG);
    if (cpid == gv_pid) {
      gv_pid = -1;
    }
  }

  if (gv_pid == -1) {
    int gv_fds[2];

    if (pipe(gv_fds) != 0) {
      perror("pipe()");
    }

    if ((gv_pid = fork()) < 0) {
      perror("fork()");
    } else if (gv_pid == 0) {
      /* This is the child */
      close(gv_fds[1]);
      close(STDIN_FILENO);
      if (dup2(gv_fds[0], STDIN_FILENO) != STDIN_FILENO) {
	perror("dup2() != STDIN_FILENO.");
      }
      close(gv_fds[0]);
      /* hopefully this has redirected gv_fds[0] to STDIN */
      execlp("geomview", "geomview", "-", NULL);
    } else {
      /* This is the parent */
      close(gv_fds[0]);
      global_gvf = fdopen(gv_fds[1], "w");
    }    
    make_logo(global_gvf);
  }
#endif  
      
  gvf = global_gvf;

  if (DIM_OF_WORLD > 3) {
    fprintf(gvf, "(if (not (emodule-isrunning NDview))\n"
	    "  (emodule-start NDview))\n");
    /* Delete the default camera */
    fprintf(gvf, "(if (real-id \"Camera\") (delete \"Camera\"))\n");
  }
  fflush(gvf);
  
  if (get_est) {
      
    if (est_min > est_max) {
      est_min = 1e8;
      est_max = 0.0;

      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	REAL est = get_est(el_info->el);

	est_min = MIN(est_min, est);
	est_max = MAX(est_max, est);
      } TRAVERSE_NEXT();
    }
      
    if (est_min > est_max) {
      est_min = est_max = 0.0;
    }

    est_data.get_est = get_est;
    est_data.min     = est_min;
    est_data.max     = est_max;
    est_data.range   = MAX(est_max - est_min, DBL_EPSILON);

    MSG("est min/max: %e/%e\n", est_min, est_max);

    el_color_fct = est_color_fct;
  }

  if (u_h) {
    if (uh_min > uh_max) {
      uh_min = 1e8;
      uh_max = 0.0;

      FOR_ALL_DOFS(u_h->fe_space->admin, 
		   uh_min = MIN(uh_min, u_h->vec[dof]);
		   uh_max = MAX(uh_max, u_h->vec[dof]));
      
      if (uh_min > uh_max)
	uh_max = uh_min = 0.0;
    }

    uh_data.max      = uh_max;
    uh_data.min      = uh_min;
    uh_data.range    = MAX(uh_max - uh_min, DBL_EPSILON);
    uh_data.uh       = u_h;
    uh_data.bas_fcts = u_h->fe_space->bas_fcts;
    uh_data.uh_loc   = uh_loc = get_el_real_vec(uh_data.bas_fcts);

    uh_nograph_fct = uh_nograph_vertex_color_fct;
  }

  if (domesh) {
    static const int location[MAX(DIM_OF_WORLD,3)] = { 0, 0, 0, };

    fprintf(gvf, "(read geometry { define ALBERTA-MESH\n");

    mesh2off(gvf, mesh,
	     uh_nograph_fct, &uh_data, FILL_NOTHING,
	     el_color_fct, &est_data, FILL_NOTHING,
	     ref_deg, true /* flat_shading */, GV_BINARY);

    fprintf(gvf, "})\n\n");

    lookat(gvf, DIM_OF_WORLD, mesh->diam, location, 
	   mesh->name ? mesh->name : "ALBERTA-MESH", "ALBERTA-MESH");

  } else {
    mesh = u_h->fe_space->mesh;
  }

  if (u_h) {
    static const int location[MAX(DIM_OF_WORLD,3)] = { 4, 0, 0, };

    fprintf(gvf, "(read geometry { define ALBERTA-VALUES\n");
    mesh2off(gvf, mesh,
	     uh_vertex_color_fct, &uh_data, FILL_NOTHING,
	     el_color_fct, &est_data, FILL_NOTHING,
	     ref_deg, false /* flat_shading */, GV_BINARY);
    fprintf(gvf, "})\n\n");

    lookat(gvf, DIM_OF_WORLD, mesh->diam, location,
	   u_h->name ? u_h->name : "ALBERTA values", "ALBERTA-VALUES");

    MSG("uh min/max: %e/%e\n", uh_min, uh_max);
  }
  
  if (u_loc && !u_h) {
    static const int location[MAX(DIM_OF_WORLD,3)] = { 0, 4, 0, };
    struct u_data u_data;
    char name[1024];
    int dim = mesh->dim;

    if (u_min > u_max) {
      u_min = 1e8;
      u_max = 0.0;

      u_min = 1e8;
      u_max = 0.0;

      TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|fill_flags) {
	bool affine = true;
	int  ni, nni;
	REAL uval;
	
	if (parametric &&
	    !(affine = !parametric->init_element(el_info, parametric))) {
	  nni = n_nodes;
	} else {
	  nni = N_VERTICES(dim);
	}

	for (ni = 0; ni < nni; ni++) {
	  uval = u_loc(el_info, nodes[ni], ud);

	  u_min = MIN(u_min, uval);
	  u_max = MAX(u_max, uval);
	}

      } TRAVERSE_NEXT();

      if (u_min > u_max)
	u_max = u_min = 0.0;
    }

    u_data.max   = u_max;
    u_data.min   = u_min;
    u_data.range = MAX(u_max - u_min, DBL_EPSILON);
    u_data.u_loc = u_loc;
    u_data.ud    = ud;

    fprintf(gvf, "(read geometry { define ALBERTA-SOLUTION\n");
    mesh2off(gvf, mesh,
	     u_vertex_color_fct, &u_data, fill_flags,
	     el_color_fct, &est_data, FILL_NOTHING,
	     ref_deg, false /* flat_shading */, GV_BINARY); 
    fprintf(gvf, "})\n\n");
    snprintf(name, 1024, "%s exact solution",
	     u_h->name ? u_h->name : "ALBERTA");

    lookat(gvf, DIM_OF_WORLD, mesh->diam, location, name, "ALBERTA-SOLUTION"); 

    MSG("u min/max: %e/%e\n", u_min, u_max);
  }

  if (u_loc && u_h) {
    static const int location[MAX(DIM_OF_WORLD,3)] = { 4, 4, 0, };
    struct err_data err_data;
    char name[1024];
    int dim = mesh->dim;

    err_data.bas_fcts = u_h->fe_space->bas_fcts;
    err_data.uh_loc   = uh_loc;
    err_data.uh       = u_h;

    err_data.min = 1e8;
    err_data.max = 0.0;

    err_data.min = 1e8;
    err_data.max = 0.0;

    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|fill_flags) {
      bool affine = true;
      int  ni, nni;
      REAL value;

      if (parametric &&
	  !(affine = !parametric->init_element(el_info, parametric))) {
	nni = n_nodes;
      } else {
	nni = N_VERTICES(dim);
      }  

      fill_el_real_vec(err_data.uh_loc, el_info->el, err_data.uh);
	
      for (ni = 0; ni < nni; ni++) {
	value = u_loc(el_info, nodes[ni], ud);
	value -= eval_uh(nodes[ni], err_data.uh_loc, err_data.bas_fcts);
	
	value = fabs(value);

	err_data.min = MIN(err_data.min, value);
	err_data.max = MAX(err_data.max, value);
      }

    } TRAVERSE_NEXT();

    if (err_data.min > err_data.max) {
      err_data.max = err_data.min = 0.0;
    }

    err_data.range = MAX(err_data.max - err_data.min, DBL_EPSILON);
    err_data.u_loc = u_loc;
    err_data.ud    = ud;

    fprintf(gvf, "(read geometry { define ALBERTA-ERROR\n");
    mesh2off(gvf, mesh,
	     err_vertex_color_fct, &err_data, fill_flags,
	     el_color_fct, &est_data, fill_flags,
	     ref_deg,
	     false /* flat_shading */,
	     GV_BINARY); 
    fprintf(gvf, "})\n\n");

    snprintf(name, 1024, "%s error", u_h->name ? u_h->name : "ALBERTA");

    lookat(gvf, DIM_OF_WORLD, mesh->diam, location, name, "ALBERTA-ERROR");

    MSG("err min/max: %e/%e\n", err_data.min, err_data.max);
  }
  
#if 0
  if (pclose(gvf) != 0) {
    WARNING("Error stopping conversation with geomview: %s.\n",
	    strerror(errno));
  }
  global_gvf = NULL;
#endif

  MSG("diam: "FORMAT_DOW"\n", EXPAND_DOW(mesh->diam));

  if (uh_loc) {
    free_el_real_vec(uh_loc);
  }
}
