#define PROG_NAME "stmesh_view"
#define PROG_DESC "visualization of a quantized topological mesh"
#define PROG_VERS "1.0"

#define stmesh_view_C_COPYRIGHT \
  "Copyright © 2015 by State University of Campinas (UNICAMP)"

/* Last edited on 2015-11-16 01:09:47 by stolfilocal */

#define PROG_HELP \
  "  " PROG_NAME " \\\n" \
  "    -eps {EPS} \\\n" \
  "    -format { ascii | binary } \\\n" \
  "    [ -nfGuess {GUESSED_NUM_FACES} ] \\\n" \
  "    [ -title {TITLE} ] \\\n" \
  "    [ -sliceFile {SLICE_FILE} ] .. \\\n" \
  "    [ -noView ] \\\n" \
  "    " argparser_help_info_HELP " \\\n" \
  "    [ [ < ] {INFILE} ]"

#define PROG_INFO \
  "NAME\n" \
  "  " PROG_NAME " - " PROG_DESC "\n" \
  "\n" \
  "SYNOPSIS\n" \
  PROG_HELP "\n" \
  "\n" \
  "DESCRIPTION\n" \
  "  The program reads an unstructured triangle mesh from an STL file {INFILE}, extracts" \
  " its topological structure, and displays it for interactive viewing.  Optionally, it" \
  " also shows one or more flat polygons, such as slices of the mesh by planes.\n" \
  "\n" \
  "  " stmesh_STL_read_INFO "\n" \
  "\n" \
  "  The program does not detect (or care) about purely geometric problems like" \
  " two distinct triangles or two distinct edges whose interiors intersect.  It also does not check" \
  " presently whether every vertex has the manifold topology (at most one ring or" \
  " one fan of triangles incident to it).\n" \
  "\n" \
  "  The mesh is displayed interactively.  The object can be rotated around the center" \
  " of interest.  Manifold edges (those that are incident to exactly two triangles) are" \
  " drawn in black.  Other edges are drawin in orange-red.\n" \
  "\n" \
  "INPUT FILES\n" \
  "  The mesh file {INFILE} should be in STL format, either ascii or" \
  " binary.  (See the \"-format\" option below.)  If {INFILE} is" \
  " omitted or \"-\", reads from the standard input.\n" \
  "\n" \
  "  Each {SLICE_FILE} specified with the \"-sliceFile\" keyword" \
  " is supposed to contain a planar slice of mesh, in the format" \
  " accepted by the {stmesh_section.h}" \
  " routines.  Namely, " stmesh_section_format_INFO "\n" \
  "\n" \
  "OPTIONS\n" \
  "  -eps {EPS}\n" \
  "    This mandatory argument specifies the basic unit of length" \
  " (in millimeters).  All input coordinates will be rounded" \
  " to even integer multiples of {EPS} before the topology is built.\n" \
  "\n" \
  "  -format ascii\n" \
  "  -format binary\n" \
  "    This mandatory argument specifies the format of the input STL file.\n" \
  "\n" \
  "  -nfGuess {GUESSED_NUM_FACES} \n" \
  "    This optional argument is a hint for the number of triangles that are" \
  " expected to be in the input file.  It is used by the program to preallocate" \
  " internal tables, so that they don't have to be expanded dynamically while" \
  " the file is being read.  The program is more efficient if {GUESSED_NUM_FACES} is the" \
  " actual number of triangles, or somewhat higher.  If omitted, the" \
  " program assumes \"-nfGuess " stringify(stmesh_view_nfGuess_DEFAULT) "\".\n" \
  "\n" \
  "  -title {WINDOW_TITLE} \n" \
  "    This optional argument specifies the title to be displayed at the top of the window.\n" \
  "\n" \
  "  -sliceFile {SLICE_FILE}\n" \
  "    Each occurrence of this keyword specifies a file containing a planar" \
  " polygon on some horizontal plane, typically some cross-section of" \
  " the mesh.  See the INPUT FILES section above for the format.\n" \
  "\n" \
  "  -noView \n" \
  "    If this optional flag is present, the program exits immediately after" \
  " reading the STL file and extracting its topology, without opeing any" \
  " windows.  This option can be used to check the validity of the file.\n" \
  "\n" \
  "USER INTERACTION\n" \
  "  The user can rotate the model around the current center of interest by clicking" \
  " anywhere on the window and dragging the mouse: horizontally, to turn the model" \
  " around a vertical axi, or vertically, to tilt that axis.  The following" \
  " keyboard commands are available too:\n" \
  "\n" \
  "    'q','Q'   Quit the program.\n" \
  "    'e'       Toggle the drawing of the triangle edges.\n" \
  "    'f'       Toggle the filling of triangles.\n" \
  "    's'       Toggle the display of the cross-sections.\n" \
  "    'p'       Toggle the display of the reference plane.\n" \
  "    'o'       Toggle the display of the origin ({Z=0}) plane.\n" \
  "    'Z'       Zoom in, towards the center of of interest.\n" \
  "    'z'       Zoom out, away from the center of of interest.\n" \
  "\n" \
  "DOCUMENTATION OPTIONS\n" \
  argparser_help_info_HELP_INFO "\n" \
  "\n" \
  "SEE ALSO\n" \
  "  salamic(1).\n" \
  "\n" \
  "AUTHOR\n" \
  "  Created 2015-10-01 by Jorge Stolfi, IC-UNICAMP.\n" \
  "\n" \
  "MODIFICATION HISTORY\n" \
  "  2015-11-14 Added \"-sliceFile\" option.\n" \
  "\n" \
  "POSSIBLE FUTURE IMPROVEMENTS\n" \
  "  ??? Move the center of interest by arrow keys.\n" \
  "  ??? Set the center of interest by clicking with mouse on a vertex.\n" \
  "  ??? Fix triangle orientation, normals, lights.\n" \
  "  ??? Keys to move ref plane through odd Z values, highlight cut faces and edges.\n" \
  "  ??? Use transparency.\n" \
  "  ??? Show the bounding box.\n" \
  "\n" \
  "WARRANTY\n" \
  argparser_help_info_NO_WARRANTY "\n" \
  "\n" \
  "RIGHTS\n" \
  "  " stmesh_view_C_COPYRIGHT ".\n" \
  "\n" \
  argparser_help_info_STANDARD_RIGHTS

#define stringify(x) strngf(x)
#define strngf(x) #x

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <math.h>

#include <GL/glu.h>
#include <GL/glut.h>

#include <bool.h>
#include <vec.h>
#include <jsfile.h>
#include <argparser.h>
#include <affirm.h>
#include <r3.h>

#include <stmesh.h>
#include <stmesh_STL.h>
#include <stmesh_view_GL.h>
#include <stmesh_view_paint.h>

#define INF INFINITY

#define stmesh_view_zoom_step_ratio (15.0/16.0)
  /* Zoom in/out kbd commands multiply/divide the obs distance by this amount. */

typedef struct stmesh_view_options_t
  {
    char *title; /* The window title: */

    /* Quantization: */
    float eps;             /* Vertex coords are to be rounded to even multiples of {eps}. */

    /* Input model file: */
    char *modelFile;       /* Name of file with the input STL model. */
    bool_t binary;         /* FALSE if ASCII STL format, TRUE if binary STL format. */
    int nfGuess;           /* Hint about number of triangles in the mesh. */
    
    /* The slice files: */
    string_vec_t sliceFile;  /* Names of files containing the slices. */

    bool_t noView;          /* If true, exit immediately after reading the mesh. */
  } stmesh_view_options_t;

typedef struct stmesh_view_state_t
  {
    /* The mesh: */
    stmesh_t mesh;
    
    /* The slices: */ 
    int nSlices;               /* Number of given slices. */
    stmesh_section_t **slice;  /* The slices are {slice[0..nSlices-1]}. */
    
    /* Reference plane: */
    /* ??? replace by axes ??? */
    bool_t refPlane;     /* When TRUE, show reference plane (mutable). */
    bool_t orgPlane;     /* When TRUE, show reference plane placed at z = 0 (mutable). */

    /* What to draw: */
    bool_t showEdges;  /* True to draw edges (wireframe). */
    bool_t showFaces;  /* True to fill faces. */
    bool_t showSlices; /* True to fill faces. */

    /* Current window dimensions (set by {stmesh_view_reshape_method}): */
    GLint window_HSize; /* Width (mutable). */
    GLint window_VSize; /* Height (mutable). */

    /* Observer's position relative to center of interest: */
    GLfloat azimuth;    /* Azimuth from X axis (degrees, mutable). */
    GLfloat elevation;  /* Elevation from XY plane (degrees, mutable). */
    GLfloat distance;   /* Distance (pixels, mutable). */

    GLfloat center[3];  /* Center of interest. */
    double RAD;         /* Nominal radius of mesh (for reasonable Z scale). */

    /* Mouse state: */
    int mouse_x,mouse_y; /* Position of last mouse event (mutable). */
  } stmesh_view_state_t;

static stmesh_view_state_t *stmesh_view_state = NULL; /* The state displayed in the GL window. */

/* INTERNAL PROTOTYPES */

int main(int argc, char **argv);
  /* Main program. */

stmesh_view_options_t *stmesh_view_parse_options(int argc, char **argv);
  /* Parses the command line arguments and packs them as an {options_t}. */

stmesh_view_state_t *stmesh_view_create_state
  ( stmesh_t mesh,
    stmesh_view_options_t *o
  );
  /* Creates and initializes a window data record, suitable for use
    used by the methods {stmesh_view_display_method} etc. */

stmesh_section_t **stmesh_view_read_slices(stmesh_t mesh, uint32_t nf, char *fname[]);
  /* Reads the cross-section files whose names are {fname[0..nf-1]},
    returns a vector of pointers to their descriptors.  
    Prints warnings if the slice parameters are inconsitent
    with those of the mesh. */

/* MAJOR PAINTING FUNCTIONS */

void stmesh_view_paint_everything(stmesh_view_state_t *w);
  /* Repaints the terrain according to the data and parameters in {w}. */

void stmesh_view_set_perspective(stmesh_view_state_t *w);
  /* Sets the GL perspective view parameters according to the
    window size, observer position, and bounding box data
    stored in {w}. */

void stmesh_view_set_lights(stmesh_view_state_t *w);
  /* Sets the GL lighting parameters according to the state {w}. */

/* GL WINDOW METHODS */

void stmesh_view_display_method(void);
  /* Processes redisplay requests by the window manager. */

void stmesh_view_reshape_method(int width, int height);
  /* Processes window reshape events. */

void stmesh_view_keyboard_method(unsigned char key, int x, int y);
  /* Processes keyboard events. */

void stmesh_view_passivemouse_method( int x, int y);
  /* Processes passive mouse events (no mouse clicked) */

void stmesh_view_activemouse_method( int x, int y);
  /* Processes active mouse events (mouse clicked) */

void stmesh_view_special_method(int key, int x, int y);
  /* Processes special user input events. */

/* IMPLEMENTATIONS */

int main(int argc, char **argv)
  {
    /* Initialize the GL libraries. Note that it may delete some command line args. */
    stmesh_view_GL_initialize_libraries(&argc, argv);

    /* Parse the remaining command line options: */
    stmesh_view_options_t *o = stmesh_view_parse_options(argc, argv);

    /* Get the input triangle mesh {mesh} and its {Z}-range {minZ,maxZ}: */
    bool_t even = TRUE; /* Rount to even multiples of {eps}, as in the slicer. */
    stmesh_t mesh = stmesh_STL_read(o->modelFile, o->binary, o->eps, o->nfGuess, even, FALSE);
    
    /* Do we really want to show it? */
    if (o->noView) { return 0; }

    /* Initialize the UI state: */
    stmesh_view_state = stmesh_view_create_state(mesh, o);

    /* Open the window and start interaction: */
    stmesh_view_GL_initialize_window
      ( o->title,
        stmesh_view_display_method,
        stmesh_view_reshape_method,
        stmesh_view_keyboard_method,
	stmesh_view_passivemouse_method,
	stmesh_view_activemouse_method,
        stmesh_view_special_method
      );
    stmesh_view_GL_start_viewing();

    return 0;
  }

stmesh_view_state_t *stmesh_view_create_state
  ( stmesh_t mesh,
    stmesh_view_options_t *o
  )
  {
    stmesh_view_state_t *w = notnull(malloc(sizeof(stmesh_view_state_t)), "no mem");

    w->window_HSize = stmesh_view_GL_default_window_HSize;
    w->window_VSize = stmesh_view_GL_default_window_VSize;

    /* Save the mesh in the state: */
    w->mesh = mesh;
    
    /* Read the slices and save in state: */
    w->nSlices = o->sliceFile.ne;
    w->slice = stmesh_view_read_slices(mesh, o->sliceFile.ne, o->sliceFile.e);

    /* Do not show reference planes initially: */
    w->refPlane = FALSE;
    w->orgPlane = FALSE;

    /* Draw both edges and vertices initially: */
    w->showEdges = TRUE;
    w->showFaces = TRUE;
    w->showSlices = TRUE;

    /* Initial center of interest and nominal radius of data: */
    float eps = stmesh_get_eps(mesh);
    i3_t minP, maxP;
    stmesh_get_bounding_box(mesh, &minP, &maxP);
    r3_t minR = stmesh_unround_point(&(minP), eps);
    r3_t maxR = stmesh_unround_point(&(maxP), eps);
    r3_t ctr, rad;
    int k;
    for (k = 0; k < 3; k++)
      { rad.c[k] = (maxR.c[k] - minR.c[k])/2;
        ctr.c[k] = (minR.c[k] + maxR.c[k])/2;
        w->center[k] = (GLfloat)ctr.c[k];
      }
    w->RAD = r3_norm(&rad);

    /* Initial observer's position: */
    w->azimuth = 0;
    w->elevation = 30;
    w->distance = (GLfloat)(2*w->RAD);

    return w;
  }

stmesh_section_t **stmesh_view_read_slices(stmesh_t mesh, uint32_t nf, char *fname[])
  {
    bool_t verbose = TRUE;
    
    /* Get mesh parameters for consistency checking: */
    float eps_mesh = stmesh_get_eps(mesh);
    i3_t minQ, maxQ; /* Quantized bounding box of mesh. */
    stmesh_get_bounding_box(mesh, &minQ, &maxQ);
    double fminZ = ((double)eps_mesh)*((double)minQ.c[2]); /* Min {Z} coord in mm. */
    double fmaxZ = ((double)eps_mesh)*((double)maxQ.c[2]); /* Max {Z} coord in mm. */
    
    stmesh_section_t **slice = notnull(malloc(nf*sizeof(stmesh_section_t*)), "no mem");
    
    int i;
    for (i = 0; i < nf; i++)
      { FILE *rd = open_read(fname[i], verbose);
        stmesh_section_t *sec = stmesh_section_read(rd);
        /* Check consistency with mesh parameters: */
        if (fabs(((double)sec->eps)/((double)eps_mesh) - 1.0) > 1.0e-7)
          { fprintf(stderr, "!! warning: {sec.eps} = %+23.15e", (double)sec->eps);
            fprintf(stderr, " differs from {mesh.eps} = %+23.15e\n", (double)eps_mesh); 
          }
        double fpZ = ((double)sec->eps)*((double)sec->pZ);
        if ((fpZ - fminZ < -1.0e-6) || (fpZ - fmaxZ > +1.0e-6))
          { fprintf(stderr, "!! warning: {sec.Z} = %+10.7f mm", fpZ);
            fprintf(stderr, " outside mesh {Z} range [%+10.7f _ %+10.7f]\n", fminZ, fmaxZ); 
          }
        slice[i] = sec;
        fclose(rd);
      }
    
    return slice;
  }

void stmesh_view_paint_everything(stmesh_view_state_t *w)
  {
    /* Get the corners of the model's bounding box: */
    float eps = stmesh_get_eps(w->mesh);
    i3_t minP, maxP;
    stmesh_get_bounding_box(w->mesh, &minP, &maxP);
    r3_t minR = stmesh_unround_point(&(minP),eps);
    r3_t maxR = stmesh_unround_point(&(maxP),eps);

    /* Set the GL graphics modes for depth painting: */
    glShadeModel(GL_SMOOTH);
    glDepthMask(GL_TRUE);
    glEnable(GL_DEPTH_TEST);

    /* Compute observer's position: */
    double az =  M_PI*w->azimuth/180;   /* Observer's azimuth in radians. */
    double ev =  M_PI*w->elevation/180; /* Observer's elevation in radians. */
    double ca = cos(az), sa = sin(az);
    double ce = cos(ev), se = sin(ev);
    double R = w->distance;

    GLfloat obs[3];
    obs[0] = w->center[0] + (GLfloat)(R * ca * ce);
    obs[1] = w->center[1] + (GLfloat)(R * sa * ce);
    obs[2] = w->center[2] + (GLfloat)(R * se);

    glPushMatrix();

    /* Set the view transformation matrix: */
    gluLookAt( obs[0], obs[1], obs[2], w->center[0], w->center[1], w->center[2], 0.0, 0.0, 1.0);

    /* Place the lights: */
    stmesh_view_set_lights(w);

    /* Paint the reference planes, if any: */
    if (w->refPlane)
      { stmesh_view_paint_reference_plane(&minR, &maxR, minR.c[2]);
        stmesh_view_paint_reference_plane(&minR, &maxR, (double)(w->center[2]));
        stmesh_view_paint_reference_plane(&minR, &maxR, maxR.c[2]);
      }
    if (w->orgPlane)
      { double zplane = 0;
        stmesh_view_paint_reference_plane(&minR, &maxR, zplane);
      }

    /* Paint the mesh: */
    stmesh_view_paint_mesh(w->mesh, w->showFaces, w->showEdges);

    if (w->showSlices)
      { /* Show the slices: */
        stmesh_view_paint_slices(w->mesh, w->nSlices, w->slice);
      }

    glPopMatrix();

    glDisable(GL_DEPTH_TEST);
  }

void stmesh_view_set_perspective(stmesh_view_state_t *w)
  {
    /* Set viewport: */
    glViewport(0, 0, (GLint)w->window_HSize, (GLint)w->window_VSize);

    /* Set perspective matrix: */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    float angheight = 40.0; /* Angular height of window (degrees) */
    float aspect = (float)w->window_HSize/(float)w->window_VSize;
    gluPerspective(angheight, aspect, 0.1, w->distance + 2.0*w->RAD);
    glMatrixMode(GL_MODELVIEW);
  }

void stmesh_view_set_lights(stmesh_view_state_t *w)
  {
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    double ambi = 0.3;                /* Amount of ambient light. */
    double spec = 0.0;                /* Amount of shine-scatterable light. */
    double diff = 1.0 - ambi - spec;  /* Amount of diffusible light. */

    if (ambi > 0)
      { GLfloat light_ambient[] = { (GLfloat)ambi, (GLfloat)ambi, (GLfloat)ambi, (GLfloat)1.0 };
        glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ambient);
      }

    if (diff > 0)
      { GLfloat light_diffuse[] = { (GLfloat)diff, (GLfloat)diff, (GLfloat)diff, (GLfloat)1.0 };
        glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_diffuse);
      }

    if (spec > 0)
      { GLfloat light_specular[] = { (GLfloat)spec, (GLfloat)spec, (GLfloat)spec, (GLfloat)1.0 };
        glLightfv(GL_LIGHT0, GL_SPECULAR,  light_specular);
      }

    /* Choose light's position relative to observer: */
    double daz = +30;  /* Azimuth rel to observer (degrees). */
    double dev = +30;  /* Elevation rel to observer (degrees). */
    /* Compute light  direction vector: */
    double az =  M_PI*(w->azimuth + daz)/180;   /* Light's azimuth (radians). */
    double ev =  M_PI*(w->elevation + dev)/180; /* Observer's elevation (radians). */
    double ca = cos(az), sa = sin(az);
    double ce = cos(ev), se = sin(ev);
    GLfloat dirx = (GLfloat)(ca * ce);
    GLfloat diry = (GLfloat)(sa * ce);
    GLfloat dirz = (GLfloat)se;

    /* Place light at infinity: */
    GLfloat pos[] = { dirx, diry, dirz, 0.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, pos);
  }

/* GL EVENT-HANDLING METHODS */

void stmesh_view_display_method(void)
  {
    if (stmesh_view_debug_GL) { fprintf(stderr, "+ %s\n", __FUNCTION__); }
    stmesh_view_state_t *w = stmesh_view_state;

    /* Clear the window: */
    glClearColor(0.750, 0.750, 0.750, 1.000); /* Light gray, opaque. */
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    /* Paint the terrain and stuff: */
    stmesh_view_paint_everything(w);

    /* Display the result: */
    glutSwapBuffers();
    if (stmesh_view_debug_GL) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_reshape_method(int width, int height)
  {
    if (stmesh_view_debug_GL) { fprintf(stderr, "+ %s\n", __FUNCTION__); }

    stmesh_view_state_t *w = stmesh_view_state;

    /* Save window size: */
    w->window_HSize = width;
    w->window_VSize = height;

    /* Adjust the perspective parameters to the window's current size: */
    stmesh_view_set_perspective(w);

    if (stmesh_view_debug_GL) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_keyboard_method(unsigned char key, int x, int y)
  {
    if (stmesh_view_debug_GL) { fprintf(stderr, "+ %s\n", __FUNCTION__); }

    stmesh_view_state_t *w = stmesh_view_state;

    switch (key)
      {
        case 27:
        case 'q':
        case 'Q':
          /* Quit: */
          exit(0);
          break;

        case 'e':
          /* Toggle edge drawing: */
          w->showEdges = !w->showEdges;
          glutPostRedisplay();
          break;

        case 'f':
          /* Toggle face filling: */
          w->showFaces = !w->showFaces;
          glutPostRedisplay();
          break;

        case 's':
          /* Toggle cross-section display: */
          w->showSlices = !w->showSlices;
          glutPostRedisplay();
          break;

        case 'p':
          /* Toggle reference plane: */
          w->refPlane = !w->refPlane;
          glutPostRedisplay();
          break;

        case 'o':
          /* Toggle reference plane: */
          w->orgPlane = !w->orgPlane;
          glutPostRedisplay();
          break;

        case 'Z':
          /* Zoom in, down to a limit: */
          w->distance = (GLfloat)fmax(w->distance*stmesh_view_zoom_step_ratio, w->RAD/16);
          glutPostRedisplay();
          break;

        case 'z':
          /* Zoom out, up to a limit: */
          w->distance = (GLfloat)fmin(w->distance/stmesh_view_zoom_step_ratio, w->RAD*16);
          glutPostRedisplay();
          break;
      }

    if (stmesh_view_debug_GL) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_passivemouse_method( int x, int y)
  {
    stmesh_view_state_t *w = stmesh_view_state;

    w->mouse_x = x;
    w->mouse_y = y;
  }

void stmesh_view_activemouse_method(int x, int y)
  {
    stmesh_view_state_t *w = stmesh_view_state;

    int xvec = w->mouse_x - x;
    int yvec = w->mouse_y - y;

    double az = (double)w->azimuth + xvec;
    double el = (double)w->elevation - yvec;
    el = fmax(-88.0, fmin(+88.0, el));

    w->elevation = (GLfloat)el;
    w->azimuth = (GLfloat)az;

    w->mouse_x = x;
    w->mouse_y = y;

    glutPostRedisplay();
  }

void stmesh_view_special_method(int key, int x, int y)
  {
    if (stmesh_view_debug_GL) { fprintf(stderr, "+ %s\n", __FUNCTION__); }

    stmesh_view_state_t *w = stmesh_view_state;

    switch (key)
      {
        case GLUT_KEY_UP:
          w->elevation = (GLfloat)fmin(w->elevation + 2, +88.0);
          glutPostRedisplay();
          break;

        case GLUT_KEY_DOWN:
          w->elevation = (GLfloat)fmax(w->elevation - 2, -88.0);
          glutPostRedisplay();
          break;

        case GLUT_KEY_LEFT:
          w->azimuth = w->azimuth + 2;
          glutPostRedisplay();
          break;

        case GLUT_KEY_RIGHT:
          w->azimuth = w->azimuth - 2;
          glutPostRedisplay();
          break;
      }

    if (stmesh_view_debug_GL) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

#define stmesh_view_eps_MIN (0.000001f)
  /* Minimum value of fundamental unit {eps} (mm). */

#define stmesh_view_eps_MAX (1.0f)
  /* Maximum value of fundamental unit {eps} (mm). */

#define stmesh_view_nfGuess_MAX (500*1000*1000)
  /* Default guess for the number of faces in the mesh. */

#define stmesh_view_nfGuess_DEFAULT 100000
  /* Maximum guess for the number of faces in the mesh. */

stmesh_view_options_t *stmesh_view_parse_options(int argc, char** argv)
 {
    /* Initialize argument parser: */
    argparser_t *pp = argparser_new(stderr, argc, argv);
    argparser_set_help(pp, PROG_NAME " version " PROG_VERS ", usage:\n" PROG_HELP);
    argparser_set_info(pp, PROG_INFO);
    argparser_process_help_info_options(pp);

    /* Allocate the command line argument record: */
    stmesh_view_options_t *o = notnull(malloc(sizeof(stmesh_view_options_t)), "no mem");

    /* Parse keyword parameters: */

    /* Window title: */
    if (argparser_keyword_present(pp, "-title"))
      { o->title = argparser_get_next_non_keyword(pp); }
    else
      { o->title = ( PROG_NAME " " PROG_VERS ); }

    /* Fundamental unit: */
    argparser_get_keyword(pp, "-eps");
    o->eps = (float)argparser_get_next_double(pp, stmesh_view_eps_MIN, stmesh_view_eps_MAX);

    /* Input format: */
    argparser_get_keyword(pp, "-format");
    if (argparser_keyword_present_next(pp, "ascii"))
      { o->binary = FALSE; }
    else if (argparser_keyword_present_next(pp, "binary"))
      { o->binary = TRUE; }
    else
      { argparser_error(pp, "unrecognized file format"); }

    /* Size guess: */
    if (argparser_keyword_present(pp, "-nfGuess"))
      { o->nfGuess = (int)argparser_get_next_int(pp, 0, stmesh_view_nfGuess_MAX); }
    else
      { o->nfGuess = stmesh_view_nfGuess_DEFAULT; }

    /* Slice files: */
    o->sliceFile = string_vec_new(1000);
    int nf = 0;
    while (argparser_keyword_present(pp, "-sliceFile"))
      { string_vec_expand(&(o->sliceFile), nf);
        o->sliceFile.e[nf] = argparser_get_next_non_keyword(pp);
        nf++;
      }
    string_vec_trim(&(o->sliceFile), nf);
    
    /* Read-only flag: */
    o->noView = argparser_keyword_present(pp, "-noView");
    
    /* argparser_get_keyword(pp, "-outPrefix"); */
    /* o->outPrefix = argparser_get_next_non_keyword(pp); */

    /* Parse positional arguments: */
    argparser_skip_parsed(pp);

    /* Input file name: */
    if (argparser_next_is_non_keyword(pp))
      { o->modelFile = argparser_get_next(pp); }
    else
      { /* Use standard input. */
        o->modelFile = "-";
      }

    /* Check for spurious arguments: */
    argparser_finish(pp);

    return o;
  }

