#define PROG_NAME "tomo_to_stl"
#define PROG_DESC "Convert a tomogram file to an unstructured triangle mesh STL file"
#define PROG_VERS "1.0"

#define tomo_to_stl_C_COPYRIGHT \
  "Copyright © 2016 by the State University of Campinas (UNICAMP)"

/* Last edited on 2016-04-18 19:41:23 by stolfilocal */

#define PROG_HELP \
  "  " PROG_NAME " \\\n" \
  "    -eps {EPS} \\\n" \
  "    [ -step {STEP} ] \\\n" \
  "    [ -showTetras ] \\\n" \
  "    " argparser_help_info_HELP " \\\n" \
  "    < {INFILE} \\\n" \
  "    > {OUTFILE}"

#define PROG_INFO \
  "NAME\n" \
  "  " PROG_NAME " - " PROG_DESC "\n" \
  "\n" \
  "SYNOPSIS\n" \
  PROG_HELP "\n" \
  "\n" \
  "DESCRIPTION\n" \
  "  The program reads from {INFILE} a tomogram (3-dimensional array of voxels)" \
  " representing a 3-dimensional object, and writes to {OUTFILE} an" \
  " STL file with the boundary of that object.\n" \
  "\n" \
  "  Each voxel must be a single numeric sample, that is converted to" \
  " an occupancy value between 0.0 (empty space) and 1.0 (object" \
  " interior).  Intermediate values can be used to indicate that" \
  " a voxel is partly occupied.  These values are assigned to the" \
  " center of each voxel.  The occupancy at other points of space" \
  " is defined by linear interpolation, to give a continuous scalar" \
  " field with values between 0 and 1.  The surface of the object" \
  " is defined to be the set of all points with occupancy 0.5.\n" \
  "\n" \
  "DETAILS\n" \
  "  More precisely, we assume that the voxels are axis-aligned" \
  " boxes measuring {SX} by {SY} by {SZ} millimeters.  A `grid vertex' or" \
  " simply `vertex' is a corner of some voxel of the" \
  " array.  If the array has {NX} by {NY} by {NZ} elements, then" \
  " it is assumed to span the box {D = [0 _ NX*SX] × [0 _ NY*SY] × [0 _ NZ*SZ]}.\n" \
  "\n" \
  "  To determine the object's surface, the box {D} is first" \
  " covered by a set of octahedra.  Each octahedron has an" \
  " `equator' that is a face of some voxel, and two `poles' that" \
  " are the centers of the voxels adjacent to that face.  Each" \
  " octahedron is then divided into 4 tetrahedra that span the" \
  " two poles and two consecutive voxel vertices of the equator.  The" \
  " set of all these tetrahedra is the `interpolation mesh' {M}.\n" \
  "\n" \
  "  The occupancy of each vertex is defined as the average of the" \
  " occupancies of the centers of the eight voxels that surround" \
  " it. The occupancy for an arbitrary point inside a tetrahedron {T} of" \
  " the mesh {M} is defined by affine (1st degree) interpolation of the" \
  " occupancies at the four corners of {T}.\n" \
  "\n" \
  "  The conversion from the integer sample values to occupancy values" \
  " is such that the occupancy at a pixel center is never" \
  " exactly 0.5.  Moreover, when computing the occupancy at a grid" \
  " vertex, if the average of the surrounding voxel values is" \
  " exactly 0.5, the occupancy is slightly perturbed to be" \
  " slightly greater or smaller than 0.5.\n" \
  "\n" \
  "  Inside each tetrahedron {T}, therefore, the obect's surface -- the" \
  " points where the occupancy is 0.5 -- is either empty, or a" \
  " non-degenerate planar polygon (triangle or quadrilateral).  The" \
  " second case occurs if and only if the occupancies at the corners" \
  " of {T} include values that bracket the threshold 0.5.  If the" \
  " itersection is a quadrilateral, the program splits it into two" \
  " triangles by its shorter diagonal. In any case, any such" \
  " triangles are written to the output STL file.\n" \
  "\n" \
  "  In order to avoid very small triangles, every triangle corner" \
  " is quantized to an EVEN integer multiple of the given parameter {EPS}.  Triangles" \
  " that have the same vertices after quantization are eliminated.  Since" \
  " the tetrahedra have the same 'fat' geometry, it is hoped that this" \
  " filtering will be sufficient to eliminate triangles that are too thin.\n" \
  "\n" \
  "  In order to completely cover the box {D}, the given tomogram" \
  " is implicitly surrounded by a layer of padding voxels, assumed" \
  " to have slightly negative occupancy.  This adjustment ensures" \
  " that the interpolated occupancy function is strictly less" \
  " than 0.5 at every point on the boundary of {D}.  This in" \
  " turn ensures that the extracted surface is closed and" \
  " lies strictly inside {D}.\n" \
  "\n" \
  "INPUT FILE FORMAT\n" \
  "  The input file format is defined by the function {ppv_array_read} in" \
  " file {ppv_io.h} of J. Stolfi's {libppv} library.  Namely:\n" \
  "\n" \
  "  " ppv_FILE_FORMAT_INFO "\n" \
  "\n" \
  "  The array must have three indices ({N = 3}), which are, in" \
  " order, {Z}, {Y}, and {X}.  This means that all voxels of each" \
  " horizontal plane occur inconsecutive position, and ditto for" \
  " the voxels in any row parallel to the {X} axis.\n" \
  "\n" \
  "OUTPUT FILE FORMAT\n" \
  "  The output file is in the standard STL format.\n" \
  "\n" \
  "OPTIONS\n" \
  "  -step {STEP}\n" \
  "    This optional argument defines the dimensions of a" \
  " voxel along the X, Y, and Z axes, respectively.  If omitted, the" \
  " program assumes \"-step 1.0\"; that is, each voxel is a cube with 1 mm sides.\n" \
  "\n" \
  "  -eps {EPS}\n" \
  "    This mandatory argument specifies the basic unit of length" \
  " (in millimeters).  All output vertex coordinates (after scaling by" \
  " the \"-step\" factor) will be rounded to EVEN integer multiples" \
  " of {EPS}.  When choosing {EPS}, note that, before scaling, the" \
  " triangles have diameters less than 1.\n" \
  "\n" \
  "  -showTetras\n" \
  "    This optional flag tells the program to output every" \
  " tetrahedron of the mesh that straddles the object's surface; that" \
  " is, with at least one corner on each side.  Each tetrahedron is" \
  " contracted to 1/2 of its size.  If omitted, the object's surface" \
  " is output instead.\n" \
  "\n" \
  "DOCUMENTATION OPTIONS\n" \
  argparser_help_info_HELP_INFO "\n" \
  "\n" \
  "SEE ALSO\n" \
  "  salamic(1).\n" \
  "\n" \
  "AUTHOR\n" \
  "  Created 2016-03-09 by Jorge Stolfi, IC-UNICAMP.\n" \
  "\n" \
  "MODIFICATION HISTORY\n" \
  " 2016-03-09 by J. Stolfi: Created.\n" \
  " 2016-03-10 by J. Stolfi: Split out the marching octahera code.\n" \
  " 2016-03-10 by J. Stolfi: Vertex quantization, parameter \"-eps\".\n" \
  " 2016-03-10 by J. Stolfi: Option \"-showTetras\".\n" \
  "\n" \
  "WARRANTY\n" \
  argparser_help_info_NO_WARRANTY "\n" \
  "\n" \
  "RIGHTS\n" \
  "  " tomo_to_stl_C_COPYRIGHT ".\n" \
  "\n" \
  argparser_help_info_STANDARD_RIGHTS

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

#include <bool.h>
#include <affirm.h>
#include <jsfile.h>
#include <jsmath.h>
#include <argparser.h>
#include <r3.h>
#include <i3.h>
#include <ppv_array.h>
#include <ppv_io.h>
/* #include <stmesh_utils.h> */

#include <mroc.h>
#include <mroc_ppv.h>

/* !!! Optimize by building an expanded bit array to skip empty and solid areas. !!! */
/* ??? Add option to specify the output precison. ??? */

/* COMMAND-LINE OPTIONS */

typedef struct t2s_options_t
  { double step;       /* Voxel dimensions (mm). */
    bool_t showTetras; /* If {TRUE}, output the tetrahedra that straddle the boundary. */
    float eps;         /* Vertex coords are to be rounded to even multiples of {eps}. */
  } t2s_options_t;


typedef struct t2s_tomogram_t
  { ppv_array_t a;  /* Descriptor of sample array (only first 3 axes are non-trivial). */
  } t2s_tomogram_t;

#define t2s_PADDING_VAL (-1.0e-6)
  /* Negative occupancy for padding cells, to ensure that surface is closed
    and inside the domain box {D}. */

#define t2s_SIZE_MAX (1000)
  /* Max tomogram size along any axis. */

/* INTERNAL PROTOTYPES */

t2s_options_t *t2s_parse_options(int32_t argc, char **argv);
  /* Parses the command line arguments and packs them as an {t2s_options_t}. */
  
t2s_tomogram_t *t2s_read_file(char *inFile); 

void t2s_marching_octahedra
  ( char *outFile, 
    double step,
    bool_t showTetras,
    float eps,
    t2s_tomogram_t *tm
  );
  /* Extracts the surface of the object from tomogram {tm} by the marching octahedra algorithm.
    Writes to file {wr} an STL description of the 0.5 isosurface. See {mroc_ppv_process_array} for detail. */

void t2s_classify_tetra_vertices(double f[], int32_t *niP, int32_t ki[], int32_t *noP, int32_t ko[]);
  /* Scans the values {f[0..3]} counting the numbers {ni,no} of corners
    of the tetrahedron that are respectively inside and outside the object.  Stores
    the indices of those corners in {ki[0..ni-1]} and {ko[0..no-1]}, and their 
    counts in {*niP,*noP}. */
    
void t2s_write_surface_in_tetra
  ( FILE *wr, 
    r3_t p[], 
    double f[], 
    float eps,
    int32_t *ntP,
    int32_t *neP
  );
  /* Assumes {f[0..3]} are the occupancy values at the 
    four corners {p[0..3]} of a tetrahedron.
    If the tetrahedron straddles the 0.5 isosurface,
    writes the relevant part of that isosurface (one or two triangles)
    into {wr}. 
    
    The triangle corners are rounded to EVEN integer multiples
    of {eps}, and the triangle is discarded if two vertices coincide
    as a result.
    
    Adds to {*ntP} the number of triangles written out,
    and to {*neP} the number of triangles discarded. */
    
#define t2s_TETRA_SHRINK (0.8)
  /* Tetrahedron shrinking factor for {t2s_write_tetra_faces}. */

void t2s_write_tetra_faces
  ( FILE *wr, 
    r3_t p[], 
    double f[], 
    int32_t *ntP
  );
  /* Assumes {f[0..3]} are the occupancy values at the 
    four corners {p[0..3]} of a tetrahedron.
    If the tetrahedron straddles the 0.5 isosurface,
    writes into {wr} the faces of the tetrahedron, 
    after shrinking it by some factor. 
    
    Adds to {*ntP} the number of triangles created. */

void t2s_write_i3_triangle
  ( FILE *wr, 
    i3_t *p0, 
    i3_t *p1, 
    i3_t *p2,
    float eps,
    int32_t *ntP,
    int32_t *neP
  );
  /* Checks whether the three quantized points {p0,p1,p2}
    are all distinct.  If so, writes to {wr} the triangle with corners {p0,p1,p2},
    after multiplying all coordinates by {eps}, and increments {*ntP}.
    Otherwise writes nothing and increments {*neP}. */

void t2s_write_r3_triangle
  ( FILE *wr, 
    r3_t *p0, 
    r3_t *p1, 
    r3_t *p2
  );
  /* Writes the triangle {p0,p1,p2} to [wr}. */
 
r3_t t2s_unround_point(i3_t *p, float eps);
  /* Converts the quantized point {*p} to a non-quantized point,
    with coordinates in mm, by multiplying it by {eps}. */

r3_t t2s_compute_normal(r3_t *p0, r3_t *p1, r3_t *p2);
  /* Computes the normal of the triangle with vertices {p0,p1,p2}. */
 
i3_t t2s_edge_crossing(r3_t *p0, double f0, r3_t *p1, double f1, float eps);
  /* Assumes that {f0,f1} are the function values at points {p0,p1},
    one of them less than 0.5, the other one greater than 0.5. 
    Computes the point {q} along that segment where the function
    is exactly 0.5, assuming affine interpolation. Returns
    {q} with coordinates expressed as EVEN integer multiples of {eps},
    with proper rounding. */
    
int32_t main(int32_t argc,char** argv);

/* IMPLEMENTATIONS */

int32_t main(int32_t argc, char** argv)
  {
    t2s_options_t *o = t2s_parse_options(argc, argv);
    
    t2s_tomogram_t *tm = t2s_read_file("-");
    
    t2s_marching_octahedra("-", o->step, o->showTetras, o->eps, tm);
    
    return 0;
  }

t2s_tomogram_t *t2s_read_file(char *inFile)
  {
    t2s_tomogram_t *tm = notnull(malloc(sizeof(t2s_tomogram_t)), "no mem");
    FILE *rd = open_read(inFile, TRUE);
    
    tm->a = ppv_read_array(rd, 32);
    
    /* Size check for safety: */
    int32_t NA = ppv_array_NAXES;
    int32_t ka;
    for (ka = 0; ka < NA; ka++) 
      { if (ka < 3)
          { demand(tm->a.size[ka] <= t2s_SIZE_MAX, "tomogram is too big");
            demand(tm->a.size[ka] >= 1, "tomogram is empty");
          }
        else
          { demand(tm->a.size[ka] == 1, "tomogram has spurious dimensions"); }
      }
    
    fclose(rd); 
    return tm;
  }

void t2s_marching_octahedra
  ( char *outFile, 
    double step,
    bool_t showTetras,
    float eps,
    t2s_tomogram_t *tm
  )
  {
    fprintf(stderr, "enter %s\n", __FUNCTION__);
    
    bool_t debug = FALSE;
    bool_t verbose = TRUE;
    
    FILE *wr = open_write(outFile, TRUE);
    fprintf(wr, "solid\n");
    
    int32_t nt = 0; /* Number of triangles wrtiten out. */
    int32_t ne = 0; /* Number of triangles discarded. */
    int32_t lastz = -2; /* Last {z} coordinate seen. */
    
    /* Tetrahedron-processing function: */
    auto void tetra_proc(r3_t *p[], double f[]);
      /* Processes the tetrahedron  with corners {p[0..3]} and occupancy 
        values {f[0..3]}. */

    mroc_ppv_process_array(&(tm->a), t2s_PADDING_VAL, tetra_proc);
    
    fprintf(wr, "\n");
    fprintf(wr, "endsolid\n");
    fclose(wr);

    if (verbose) { fprintf(wr, "\n"); }
    fprintf(stderr, "%d triangles eliminated\n", ne);
    fprintf(stderr, "%d triangles written\n", nt);
    
    fprintf(stderr, "exit %s\n", __FUNCTION__);
    return;
    
    /* INTERNAL IMPLEMENTATION */
    void tetra_proc(r3_t *p[], double f[])
      {
        if (debug) { fprintf(stderr, "."); }
        bool_t newz = FALSE;

        r3_t u[4]; /* Scaled vertices. */
        int32_t k;
        for (k = 0; k < 4; k++)
          { r3_scale(step, p[k], &(u[k]));
            int32_t thz = (int32_t)(p[k]->c[2] - 0.499999);
            if (thz > lastz) { newz = TRUE; lastz = thz; }
          }
        if (newz && verbose) { fprintf(stderr, " %d", lastz); }
        if (showTetras)
          { t2s_write_tetra_faces(wr, u, f, &nt); }
        else
          { t2s_write_surface_in_tetra(wr, u, f, eps, &nt, &ne); }
      }
  }
  
void t2s_classify_tetra_vertices(double f[], int32_t *niP, int32_t ki[], int32_t *noP, int32_t ko[])
  {
    int32_t ni = 0; /* Number of corners inside the object. */
    int32_t no = 0; /* Number of corners outside the object. */
    int32_t k;
    for (k = 0; k < 4; k++)
      { assert(f[k] != 0.5); 
        if (f[k] > 0.5) 
          { ki[ni] = k; ni++; }
        else if (f[k] < 0.5) 
          { ko[no] = k; no++; }
        else
          { affirm(FALSE, "tetrahedron corner is ambiguous"); }
      }
    (*niP) = ni;
    (*noP) = no;
  }

void t2s_write_surface_in_tetra
  ( FILE *wr, 
    r3_t p[], 
    double f[], 
    float eps,
    int32_t *ntP,
    int32_t *neP
  )
  {
    /* Check for intersection: */
    int32_t ni; /* Number of corners inside the object. */
    int32_t ki[4]; /* Indices of inside corners are {ki[0..ni-1]}. */
    int32_t no; /* Number of corners outside the object. */
    int32_t ko[4]; /* Indices of outside corners are {ko[0..no-1]}. */
    
    t2s_classify_tetra_vertices(f, &ni, ki, &no, ko);
    assert(ni + no == 4);
    
    if ((ni == 0) || (ni == 4)) { return; }
    /* Tetrahedron intersects surface: */
    int32_t nq = 0;   /* Number of edges that cross the surface. */
    i3_t q[4];        /* Points where edges cross surface are {q[0..nq-1]} in cyclic order, quantized. */
    if (ni == 1)
      { /* Intersection is a single triangle: */ 
        assert(no == 3);
        int32_t k;
        for (k = 0; k < 3; k++)
          { q[nq] = t2s_edge_crossing(&(p[ki[0]]), f[ki[0]], &(p[ko[k]]), f[ko[k]], eps); nq++; }
        assert(nq == 3);
        t2s_write_i3_triangle(wr, &(q[0]), &(q[1]), &(q[2]), eps, ntP, neP);
      }
     else if (ni == 3)
      { /* Intersection is a single triangle: */ 
        assert(no == 1);
        int32_t k;
        for (k = 0; k < 3; k++)
          { q[nq] = t2s_edge_crossing(&(p[ko[0]]), f[ko[0]], &(p[ki[k]]), f[ki[k]], eps); nq++;
          }
        assert(nq == 3);
        t2s_write_i3_triangle(wr, &(q[0]), &(q[1]), &(q[2]), eps, ntP, neP);
      }
    else if (ni == 2)
      { /* Intersection is a quadrilateral. */
        /* Compute the 4 corners in cyclic order: */
        q[nq] = t2s_edge_crossing(&(p[ki[0]]), f[ki[0]], &(p[ko[0]]), f[ko[0]], eps); nq++;
        q[nq] = t2s_edge_crossing(&(p[ki[0]]), f[ki[0]], &(p[ko[1]]), f[ko[1]], eps); nq++;
        q[nq] = t2s_edge_crossing(&(p[ki[1]]), f[ki[1]], &(p[ko[1]]), f[ko[1]], eps); nq++;
        q[nq] = t2s_edge_crossing(&(p[ki[1]]), f[ki[1]], &(p[ko[0]]), f[ko[0]], eps); nq++;
        assert(nq == 4);
        /* Choose diagonal to split: */
        if (i3_dist_sqr(&(q[0]), &(q[2])) < i3_dist_sqr(&(q[1]), &(q[3])))
          { /* Split by 0-2: */
            t2s_write_i3_triangle(wr, &(q[0]), &(q[1]), &(q[2]), eps, ntP, neP);
            t2s_write_i3_triangle(wr, &(q[0]), &(q[2]), &(q[3]), eps, ntP, neP);
          }
        else
          { /* Split by 1-3: */
            t2s_write_i3_triangle(wr, &(q[0]), &(q[1]), &(q[3]), eps, ntP, neP);
            t2s_write_i3_triangle(wr, &(q[1]), &(q[2]), &(q[3]), eps, ntP, neP);
          }
      }
    else
      { fatalerror("invalid {ni,no}"); }
  }
 
void t2s_write_tetra_faces
  ( FILE *wr, 
    r3_t p[], 
    double f[], 
    int32_t *ntP
  )
  {
    /* Check for intersection: */
    int32_t ni; /* Number of corners inside the object. */
    int32_t ki[4]; /* Indices of inside corners are {ki[0..ni-1]}. */
    int32_t no; /* Number of corners outside the object. */
    int32_t ko[4]; /* Indices of outside corners are {ko[0..no-1]}. */
    
    t2s_classify_tetra_vertices(f, &ni, ki, &no, ko);
    assert(ni + no == 4);

    if ((ni == 0) || (ni == 4)) { return; }
    /* Tetrahedron intersects surface: */

    /* Compute barycenter: */
    r3_t ctr;        /* Barycenter of tetrahedron. */
    r3_t p01, p23;
    r3_mix(0.5, &(p[0]), 0.5, &(p[1]), &p01);
    r3_mix(0.5, &(p[2]), 0.5, &(p[3]), &p23);
    r3_mix(0.5, &p01, 0.5, &p23, &ctr);

    /* Srink the tetrahedron towards the center: */
    r3_t q[4];       /* Corners of shrunk tetrahedron. */
    int32_t k;
    double vfac = t2s_TETRA_SHRINK;
    double cfac = 1 - vfac; 
    for (k = 0; k < 4; k++) { r3_mix(cfac, &ctr, vfac, &(p[k]), &(q[k]));  }
    
    /* Write the faces of the srunk tetrahedron: */ 
    for (k = 0; k < 4; k++)
      { int32_t k1 = (k+1) % 4;
        int32_t k2 = (k+2) % 4;
        int32_t k3 = (k+3) % 4;
        t2s_write_r3_triangle(wr, &(q[k1]), &(q[k2]), &(q[k3])); (*ntP)++;
      }
  }
    
#define t2s_MAX_QUANTIZED (10000000)
  /* Safety parameter: max abs value of quantized coordinate, to avoid overflows. */

i3_t t2s_edge_crossing(r3_t *p0, double f0, r3_t *p1, double f1, float eps)
  {
    /* Compute the relative position {r} of crossing point along segment: */
    double df = f1 - f0; 
    demand(df != 0, "same value");
    double r = (0.5 - f0)/df;
    demand ((r > 0) && (r < 1), "does not cross 0.5");

    i3_t q; /* Crossing point. quantized. */
    int32_t j;
    for (j = 0; j < 3; j++)
      { /* Compute crossing point coordinate {qj}: */
        float qj = (float)((1-r)*p0->c[j] + r*p1->c[j]);
        /* Round it to even integer multiple of {eps}: */
        q.c[j] = (int32_t)iround(qj, eps, 2, 0, INT32_MAX); 
        if (abs(q.c[j]) > t2s_MAX_QUANTIZED)
          { fprintf(stderr, "** quantized coordinate %+11.7f --> %d too big; use larger {eps}\n", qj, q.c[j]); 
            assert(FALSE);
          }
      }
    return q;
  }

void t2s_write_i3_triangle
  ( FILE *wr, 
    i3_t *p0, 
    i3_t *p1, 
    i3_t *p2,
    float eps,
    int32_t *ntP,
    int32_t *neP
  )
  {
    /* Check for repeated vertices: */
    if (i3_eq(p0, p1) || i3_eq(p1, p2) || i3_eq(p2, p0)) { (*neP)++; return; }
    
    /* Unround and write out: */
    r3_t pf0 = t2s_unround_point(p0, eps);
    r3_t pf1 = t2s_unround_point(p1, eps);
    r3_t pf2 = t2s_unround_point(p2, eps);
    t2s_write_r3_triangle(wr, &pf0, &pf1, &pf2); (*ntP)++;      
  }
     
r3_t t2s_unround_point(i3_t *p, float eps)
  { 
    r3_t r;
    r.c[0] = ((double)eps)*((double)p->c[0]);
    r.c[1] = ((double)eps)*((double)p->c[1]);
    r.c[2] = ((double)eps)*((double)p->c[2]);
    return r;
  }

#define t2s_eps_MIN (0.001)
  /* Minimum value of fundamental unit {eps} (mm).
    Must be such that coordinates that differ by this 
    much are printed differently by {t2s_write_r3_triangle}. */

#define t2s_eps_MAX (1.00)
  /* Maximum value of fundamental unit {eps} (mm). Note that good
    triangles are smaller than 1 voxel, so {eps} that is larger than
    {0.05} or so can be used only if the {step} is large too. */

#define t2s_step_MIN (0.001)
  /* Minimum value of voxel size (mm). */

#define t2s_step_MAX (200.00)
  /* Maximum value of voxel size (mm). */

void t2s_write_r3_triangle
  ( FILE *wr, 
    r3_t *p0, 
    r3_t *p1, 
    r3_t *p2
  )
  {
    /* Compute normal: */
    r3_t d = t2s_compute_normal(p0, p1, p2);
    
    /* Write face: */
    fprintf(wr, "\n");
    fprintf(wr, "facet\n");
    r3_gen_print(wr, &d, "%+7.4f", "normal ", " ", "\n");
    fprintf(wr, "outer loop\n");
    r3_gen_print(wr, p0, "%8.3f", "  vertex ", " ", "\n");
    r3_gen_print(wr, p1, "%8.3f", "  vertex ", " ", "\n");
    r3_gen_print(wr, p2, "%8.3f", "  vertex ", " ", "\n");
    fprintf(wr, "endloop\n");
    fprintf(wr, "endfacet\n");
  }
  
r3_t t2s_compute_normal(r3_t *p0, r3_t *p1, r3_t *p2)
  {
    r3_t a, b, d;
    r3_sub(p1, p0, &a);
    r3_sub(p2, p0, &b);
    r3_cross(&a, &b, &d);
    double len = r3_dir(&d, &d);
    if (len < 1.0e-6)
      { fprintf(stderr, "!! degenerate triangle -- defaulting normal\n");
        r3_gen_print(stderr, p0, "%.4f", "  p0 = ( ", " ", " )\n");
        r3_gen_print(stderr, p1, "%.4f", "  p1 = ( ", " ", " )\n");
        r3_gen_print(stderr, p2, "%.4f", "  p2 = ( ", " ", " )\n");
        r3_gen_print(stderr, &a, "%.4f", "  a  = ( ", " ", " )\n");
        r3_gen_print(stderr, &b, "%.4f", "  b  = ( ", " ", " )\n");
        d = (r3_t){{ 0.0, 0.0, 1.0 }};
      }
    return d;
  }
  
t2s_options_t *t2s_parse_options(int32_t 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: */
    t2s_options_t *o = (t2s_options_t *)malloc(sizeof(t2s_options_t)); 
    
    /* Parse keyword parameters: */
    
    /* Debug tetrahedra option: */
    o->showTetras = argparser_keyword_present(pp, "-showTetras");
    
    /* Actual voxel dimensions: */
    if (argparser_keyword_present(pp, "-step"))
      { o->step = argparser_get_next_double(pp, t2s_step_MIN, t2s_step_MAX); }
    else
      { o->step = 1.0; }

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

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

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