#define PROG_NAME "make_tesla_valve"
#define PROG_DESC "Create a tomogram of a Tesla valve"
#define PROG_VERS "1.0"

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

/* Last edited on 2016-04-22 19:38:44 by stolfilocal */

#define PROG_HELP \
  "  " PROG_NAME " \\\n" \
  "    -objectSize {SX} {SY} {SZ} \\\n" \
  "    -voxelSize {VSIZE} \\\n" \
  "    -tubeDiameter {TUBE_DIAM} \\\n" \
  "    -helixDiameter {HELIX_DIAM} \\\n" \
  "    -helixTurns {HELIX_TURNS} \\\n" \
  "    -numStages {NUM_STAGES} \\\n" \
  "    -stagesPerTurn {STAGES_PER_TURN} \\\n" \
  "    -loopRadius {LOOP_RADIUS} \\\n" \
  "    [ -loopTwist {LOOP_TWIST} ] \\\n" \
  "    [ -loopExtent {LOOP_EXTENT} ] \\\n" \
  "    [ -cups {BOTCUP} {TOPCUP} ] \\\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 writes to standard output a tomogram (3D voxel" \
  " array) containing an antialiased model of a Tesla valves.\n" \
  "\n" \
  "OUTPUT FILE FORMAT\n" \
  "  The output file format is defined by the function {ppv_array_write} in" \
  " file {ppv_io.h} of J. Stolfi's {libppv} library.  Namely:\n" \
  "\n" \
  "  " ppv_FILE_FORMAT_INFO "\n" \
  "\n" \
  "  The array will 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" \
  "OPTIONS\n" \
  "  -objectSize {SX} {SY} {SZ}\n" \
  "    This mandatory argument defines the size (in millimeters) of the object's bounding box" \
  " along the X, Y, and Z axes, respectively.  Parts of the object that" \
  " fall outside this range will be clipped\n" \
  "\n" \
  "  -voxelSize {VSIZE}\n" \
  "    This mandatory argument defines the size of a voxel in millimeters.\n" \
  "\n" \
  "  -tubeDiameter {TUBE_DIAM}\n" \
  "    This mandatory argument specifies the inner diameter" \
  " of the tubes in millimeters.\n" \
  "\n" \
  "  -helixDiameter {HELIX_DIAM}\n" \
  "    This mandatory argument specifies the diameter" \
  " of the helix that is the midline of the main tube.\n" \
  "\n" \
  "  -helixTurns {HELIX_TURNS}\n" \
  "    This mandatory argument specifies the number (possibly fractional) of full turns" \
  " of the helix that is the midline of the main tube.  It must be" \
  " sufficient to accomodate all stages.\n" \
  "\n" \
  "  -numStages {NUM_STAGES}\n" \
  "    This mandatory argument specifies the total number of stages" \
  " (backflow loops) in the valve.  If 0, the valve will have only the" \
  " main channel.\n" \
  "\n" \
  "  -stagesPerTurn {STAGES_PER_TURN}\n" \
  "    This mandatory argument specifies the number of stages (backflow loops)" \
  " for each turn of the helical main tube.\n" \
  "\n" \
  "  -loopExtent {LOOP_EXTENT}\n" \
  "    This optional argument specifies the relative extent of the" \
  " backflow loop compared to the nominal extent of the main channel" \
  " in each stage of the valve.  It is normally smaller than 1.0.  If too" \
  " large, the channels may run into each other.  If omitted, it defaults to" \
  " " stringify(mtv_loopExtent_DEFAULT) ".\n" \
  "\n" \
  "  -loopRadius {LOOP_RADIUS}\n" \
  "    This mandatory argument specifies the radius of the midline" \
  " of the terminal part of the backflow channel.\n" \
  "\n" \
  "  -loopTwist {LOOP_TWIST}\n" \
  "    This optional argument specifies the twist (in degrees) of the backflow loop" \
  " around the main channel.  If not given, the program assumes zero twist.\n" \
  "\n" \
  "  -cups {BOTCUP} {TOPCUP}\n" \
  "    This optional argument specifies whether to place cups" \
  " at the top and bottom ends of the valve. The values are" \
  " either 'T','t','Y','y','1' for TRUE, 'F','f','N','n','0' for" \
  " FALSE.  If omitted, assumes \"-cups F F\" (no cups).\n" \
  "\n" \
  "DOCUMENTATION OPTIONS\n" \
  argparser_help_info_HELP_INFO "\n" \
  "\n" \
  "SEE ALSO\n" \
  "  tomo_to_stl(1), salamic(1).\n" \
  "\n" \
  "AUTHOR\n" \
  "  Created 2016-03-10 by Jorge Stolfi, IC-UNICAMP.\n" \
  "\n" \
  "MODIFICATION HISTORY\n" \
  " 2016-03-09 J. Stolfi: Created.\n" \
  " 2016-03-13 J. Stolfi: Removing voxel-based modeling to {voxm.h}.\n" \
  " 2016-03-18 J. Stolfi: Added single compact valve.\n" \
  " 2016-03-20 J. Stolfi: Added single circular-arc valve.\n" \
  " 2016-03-20 J. Stolfi: Options \"-omitLoops\", \"-tubeDiameter\", \"-cups\".\n" \
  " 2016-03-21 J. Stolfi: Size is \"-arraySize\" and 3 numbers.\n" \
  " 2016-03-21 J. Stolfi: Added \"-voxelSize\" option, dims in mm.\n" \
  " 2016-03-21 J. Stolfi: Added \"-loopStyle\" option.\n" \
  " 2016-04-02 J. Stolfi: Substantial rewrite of {voxm} library and spiral valve code.\n" \
  " 2016-04-03 J. Stolfi: Removed \"-type\" and \"-loopStyle\" options.\n" \
  " 2016-04-21 J. Stolfi: Renamed \"-diameter\" to \"-tubeDiameter\".\n" \
  " 2016-04-21 J. Stolfi: Added \"-helixDiameter\", \"-numTurns\", \"-stagesPerTurn\", \"-loopExtent\".\n" \
  " 2016-04-22 J. Stolfi: Added \"-loopRadius\", \"-loopTwist\", \"-helixTurns\".\n" \
  " 2016-04-22 J. Stolfi: Replaced \"-numTurns\" by \"-numStages\".\n" \
  " 2016-04-22 J. Stolfi: Removed \"-omitLoops\".\n" \
  "\n" \
  "WARRANTY\n" \
  argparser_help_info_NO_WARRANTY "\n" \
  "\n" \
  "RIGHTS\n" \
  "  " make_tesla_valve_C_COPYRIGHT ".\n" \
  "\n" \
  argparser_help_info_STANDARD_RIGHTS

#define stringify(x) strngf(x)
#define strngf(x) #x
  /* Hack to include non-string defines in strings.  Don't ask why 2 levels. */ 

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

#include <bool.h>
#include <affirm.h>
#include <jsfile.h>
#include <argparser.h>
#include <r3.h>
#include <i3.h>
#include <r3x3.h>
#include <r3_path.h>
#include <ppv_array.h>
#include <ppv_io.h>

#include <voxm_obj.h>
#include <voxm_splat.h>
#include <voxm_splat_tube.h>

#include <mtv_cup.h>
#include <mtv_valve_helical_tubing_make.h>

/* COMMAND-LINE OPTIONS */

typedef struct mtv_options_t
  { r3_t objectSize;       /* Size of object in {X}, {Y}, and {Z} (mm). */
    double voxelSize;      /* Size of a voxel in millimeters. */
    double tubeDiameter;   /* Inner diameter of tubing (mm). */
    double helixDiameter;  /* Diameter of helix (mm). */
    double helixTurns;     /* Number of full turns in the helical part of main channel. */
    int32_t numStages;     /* Total number of stages (backflow loops). */
    double stagesPerTurn;  /* Number of stages per turn of helix. */
    double loopRadius;     /* Radius of midline of terminal part of backflow channel (mm). */
    double loopExtent;     /* Relative extent of backflow loop in each stage. */
    double loopTwist;      /* Twist of backflow loop around main channel (degrees). */
    bool_t cups_bot;       /* {TRUE} to include the bottom cup. */
    bool_t cups_top;       /* {TRUE} to include the top cup. */
  } mtv_options_t;

#define mtv_voxelSize_MIN (0.10)
  /* Min voxel size (mm). */

#define mtv_voxelSize_MAX (2.00)
  /* Max voxel  size (mm). */

#define mtv_arraySize_MIN (100)
  /* Min tomogram size along any axis. */

#define mtv_arraySize_MAX (10000)
  /* Max tomogram size along any axis. */

#define mtv_arrayTotSize_MAX (1024*1024*(int64_t)1024)
  /* Max tomogram size along any axis. */

#define mtv_objectSize_MAX (1500.0)
  /* Max object size along any axis (mm). */

#define mtv_tubeDiameter_MIN (0.5)
  /* Min inner tube diameter (mm). */

#define mtv_tubeDiameter_MAX (50.0)
  /* Max inner tube diameter (mm). */

#define mtv_helixDiameter_MIN (0.5)
  /* Min diameter of helix (mm). */

#define mtv_helixDiameter_MAX (50.0)
  /* Max diameter of helix (mm). */

#define mtv_loopExtent_DEFAULT 0.6
  /* Default relative backflow loop extent. */

/* INTERNAL PROTOTYPES */

mtv_options_t *mtv_parse_options(int32_t argc, char **argv);
  /* Parses the command line arguments and packs them as an {mtv_options_t}. */

void mtv_valve_make(ppv_array_t *a, mtv_options_t *o);
  /* Splat into {a} a single tesla valve with optional cups. */

void mtv_splat_cups
  ( ppv_array_t *a, 
    r3_t *ctr,
    double radZ,
    double fuzzR,
    mtv_options_t *o, 
    double *tbbotZ, 
    double *tbtopZ, 
    double *rdbotZ, 
    double *rdtopZ
  );
  /* Splats the top and bottom cups (if specified in {o})
    and computes the ends of the tubing.
    
    The cups axis is vertical and goes through {ctr}. The cups span
    {radZ} above and below {ctr}. They occupy the same vertical space,
    even if they are omitted.
    
    Returns in {*tbbotZ} and {*tbtopZ} the {Z} coordinates of the bottom
    and top ends of the tubing, that lie just inside the respctive cups.
    
    Returns in {*rdbotZ} and {*rdtopZ} the {Z} coordinates of the bottom
    and top ends of the supporting rods, that lie in the bottom wall of
    the cups. */

void mtv_splat_inner_support
  ( ppv_array_t *a, 
    r3_t *ctr,
    double rdbotZ, 
    double rdtopZ,
    double cupR,
    double fuzzR,
    mtv_options_t *o
  );
  /* Splats into the tomogram {a} the supporting rods
    in the internal space between the cups. 
    The rods will span from {rdbotZ} to {rdtopZ}
    and assume that there are cups at those places,
    with a flat part of radius at least {cupR} on 
    the outer bottom. */

void mtv_splat_outer_support
  ( ppv_array_t *a, 
    r3_t *ctr,
    double rdbotZ, 
    double rdtopZ,
    double valveR,
    double plateT,
    double fuzzR,
    mtv_options_t *o
  );
  /* Splats into the tomogram {a} a square cage
    with 4 rods at the corners. The cage rods will 
    extend from {rdbotZ} to {rdtopZ}
    and the squaretop and base will have thickness 
    {plateT}, placed just beyond
    those coordinates. */ 
  
void mtv_splat_tubes
  ( ppv_array_t *a, 
    r3_t *ctr, 
    double botZ, 
    double topZ, 
    double helixR,
    double bfR,    
    double inR,
    double tubeT,
    double fuzzR,
    mtv_options_t *o,
    bool_t sub
  );
  /* Splats into the tomogram {a} the tubes of a single- or multi-stage
    Tesla valve. The valve is roughly vertical spanning from {botZ} to
    {topZ}. The valve axis goes through {ctr}. The main channel
    midline is a helix with radius {helixR} around that axis. The midline of each backflow loop
    has radius {bfR}.  The tubes have inner radius {inR} and wall thickness {tubeT}.
    The distance function saturates {fuzzR}
    from the surface. All dimensions are in multiples of the voxel size.
    If {sub} is true, subtracts a wire that clears the tube's hollow. */ 

void mtv_tomogram_write(char *outFile, ppv_array_t *a); 

int32_t main(int32_t argc,char** argv);

/* IMPLEMENTATIONS */

int32_t main(int32_t argc, char** argv)
  {
    mtv_options_t *o = mtv_parse_options(argc, argv);
    
    /* Allocate the tomogram: */
    ppv_size_t sz[ppv_array_NAXES];
    int32_t k;
    int64_t nvt = 1; /* Total number of volxels. */
    for (k = 0; k < ppv_array_NAXES; k++) 
      { if (k <3)
          { double szk = o->objectSize.c[2-k]; /* Object size (mm) alog tomogram axis {k}. */
            double nvk = ceil(szk/o->voxelSize);
            demand(nvk <= mtv_arraySize_MAX, "voxel array dimension is too big");
            sz[k] = (int32_t)nvk;
            nvt = nvt*sz[k];
          }
        else
          { sz[k] = 1; }
      }
    demand(nvt <= mtv_arrayTotSize_MAX, "voxel array is too big");
    ppv_nbits_t bps = 8;
    ppv_nbits_t bpw = 32;
    ppv_array_t a = ppv_new_array(sz, bps, bpw);

    /* Splat object: */
    mtv_valve_make(&a, o);
    
    /* Write it out: */
    mtv_tomogram_write("-", &a);
    return 0;
  }
  
void mtv_valve_make(ppv_array_t *a, mtv_options_t *o)
  { 
    /* Grab the array dimensions in voxels: */
    int32_t NX = (int32_t)a->size[2]; 
    int32_t NY = (int32_t)a->size[1]; 
    int32_t NZ = (int32_t)a->size[0]; 
    
    double vsz = o->voxelSize; /* Voxel side (mm). */
       
    double fuzzR = 1.5;     /* Half-thickness of fuzzy layer (vx). */
    
    /* The unit for most linear dimensions is the voxel side (vx). */ 
    
    r3_t ctr = (r3_t){{ 0.5*NX, 0.5*NY, 0.5*NZ }}; /* Center of the array (vx). */
    double radZ = 0.97*NZ/2;            /* Half-height of valve, with cups (vx). */
    double radXY = 0.97*fmin(NX, NY)/2; /* Max deviation allowed from axis (vx). */ 
    fprintf(stderr, "valve height = %.1f mm\n", 2*radZ*o->voxelSize);

    /* Tube parameters: */
    double tubeT = 1.2/vsz;              /* Thickness of tube wall (vx). */
    double inR = o->tubeDiameter/vsz/2;  /* Inner tube radius (vx). */

    /* Checks for proper surface extraction: */
    assert(fuzzR > 1.0); 
    demand(tubeT > fuzzR, "tube wall thickness is too small"); 
    demand(inR > fuzzR, "inner tube diameter is too small"); 
    double helixR = o->helixDiameter/2/vsz;  /* Radius of helical midline of main conduit (vx). */
    double bfR = o->loopRadius/vsz;          /* Radius of turn in the backflow chanel (vx). */
     
    /* Estimate the radius of the valve {valveR} (vx): */
    double valveinR = helixR - inR - tubeT;  /* Free space around axis of valve; may be neg (vx). */ 
    double valveotR = helixR + 2*bfR + inR + tubeT; /* Max extent of valve away from axis (vx). */
    fprintf(stderr, "valve inner radius %.2f outer radius = %.2f\n", valveinR*vsz, valveotR*vsz);
    demand(valveotR < radXY, "valve too wide for voxel array");
    
    /* Plop down the cups (actual or virtual): */
    double cupR = 13.0/vsz; /* Usable radius of threaded cups (vx). */
    double tbbotZ; /* {Z} coordinate of bottom end of valve tubing (vx). */
    double tbtopZ; /* {Z} coordinate of bottom end of valve tubing (vx). */
    double rdbotZ; /* {Z} coordinate of bottom end of supporting rods (vx). */
    double rdtopZ; /* {Z} coordinate of bottom end of supporting rods (vx). */
    mtv_splat_cups(a, &ctr, radZ, fuzzR, o, &tbbotZ, &tbtopZ, &rdbotZ, &rdtopZ);

    if (o->cups_bot && o->cups_top)
      { /* Plop down the supporting rods: */
        bool_t innerSupport = (valveinR >= cupR); /* Enough space for inner support? */
        if (innerSupport)
          { mtv_splat_inner_support(a, &ctr, rdbotZ, rdtopZ, cupR, fuzzR, o); }
        else
          { double plateT = 2.0/vsz;  /* Thickness of end plates. */
            mtv_splat_outer_support(a, &ctr, rdbotZ, rdtopZ, valveotR, plateT, fuzzR, o);
          }
      }

    /* Plop down the tubes: */
    mtv_splat_tubes(a, &ctr, tbbotZ, tbtopZ, helixR, bfR, inR, tubeT, fuzzR, o, FALSE);

    /* Clear the bore: */
    mtv_splat_tubes(a, &ctr, tbbotZ, tbtopZ, helixR, bfR, inR, tubeT, fuzzR, o, TRUE);
  }
  
void mtv_splat_cups
  ( ppv_array_t *a, 
    r3_t *ctr,
    double radZ,
    double fuzzR,
    mtv_options_t *o, 
    double *tbbotZ, 
    double *tbtopZ, 
    double *rdbotZ, 
    double *rdtopZ
  )
  {
    /* Dimensions are the same whether cups are splatted or not. */

    /* Modified for threaded cups: */
    double vsz = o->voxelSize; /* Voxel size in mm. */
    double hgt = 14.0/vsz;   /* Height of cup. */ 
    double thk = 3.0/vsz;    /* Thickness of cup wall. */
    
    /* Checks for proper surface extraction: */
    assert(fuzzR > 1.0); 

    /* Bottom cup (upside down): */
    double topZ_bcup = ctr->c[2] - radZ; /* {Z} of top of bottom cup. */
    double botZ_bcup = topZ_bcup + hgt; /* {Z} of bottom of bottom cup. */
    if (o->cups_bot)
      { mtv_cup_threaded_make(a, ctr, botZ_bcup, topZ_bcup, thk, fuzzR, vsz); }

     /* Top cup: */
    double topZ_tcup = ctr->c[2] + radZ; /* {Z} of top of top cup. */
    double botZ_tcup = topZ_tcup - hgt; /* {Z} of bottom of top cup. */
    if (o->cups_top)
      { mtv_cup_threaded_make(a, ctr, botZ_tcup, topZ_tcup, thk, fuzzR, vsz); }
    
    (*tbtopZ) = botZ_tcup + thk; /* Top {Z} of tubing. */
    (*tbbotZ) = botZ_bcup - thk; /* Botom {Z} of tubing. */
    
    (*rdtopZ) = botZ_tcup + 0.5*thk; /* Top {Z} of rods. */
    (*rdbotZ) = botZ_bcup - 0.5*thk; /* Botom {Z} of rods. */
  }

void mtv_splat_inner_support
  ( ppv_array_t *a, 
    r3_t *ctr,
    double rdbotZ, 
    double rdtopZ,
    double cupR,
    double fuzzR,
    mtv_options_t *o
  )
  { 
    /* Derived rod parameters: */
    double vsz = o->voxelSize; /* Voxel size in mm. */
    double rdmidZ = (rdbotZ + rdtopZ)/2;    /* {Z} coordinate of rod center (vx). */
    double rodH = fabs(rdtopZ - rdbotZ)/2;  /* Half-height of rod (vx). */
    double rodR = 2.0/vsz;  /* Radius of rod (vx). */
    double rodF = 0.00;     /* Radius of rod fillet (vx). */

    double cageR = cupR - 2*rodR; /* Offset of rods from axis (vx). */
    fprintf(stderr, "cage radius %.2f\n", cageR*vsz);

    auto double fuzzy_rod(r3_t *p);
      /* Indicator function for the rod. */
       
    int N = 4; /* Number of rods. */
    double a0 = 0; /* Azimuth of initial rod. */
    int i;
    for (i = 0; i < N; i++)
      { double ai = a0 + 2.0*M_PI*((double)i)/((double)N);

        /* Position and orientation of rod: */
        r3_path_state_t state;
        state.p.c[0] = ctr->c[0] + cageR*cos(ai);
        state.p.c[1] = ctr->c[1] + cageR*sin(ai);
        state.p.c[2] = rdmidZ;
        r3x3_ident(&(state.M));
       
        voxm_splat_object(a, fuzzy_rod, &state, hypot(rodH, rodR) + fuzzR, FALSE);
      }
    
    return;
      
    double fuzzy_rod(r3_t *p)
      { return voxm_obj_rod(p, rodH, rodR, rodF, fuzzR); }
  }

void mtv_splat_outer_support
  ( ppv_array_t *a, 
    r3_t *ctr,
    double rdbotZ, 
    double rdtopZ,
    double valveR,
    double plateT,
    double fuzzR,
    mtv_options_t *o
  )
  { 
    /* Derived rod parameters: */
    double vsz = o->voxelSize; /* Voxel size in mm. */
    double rdmidZ = (rdbotZ + rdtopZ)/2;    /* {Z} coordinate of rod center (vx). */
    double rodH = fabs(rdtopZ - rdbotZ)/2;  /* Half-height of rod (vx). */
    double rodR = 2.0/vsz;  /* Radius of rod (vx). */
    double rodF = 0.00;     /* Radius of rod fillet (vx). */

    double cageR = valveR + 2*rodR; /* Offset of rods from axis (vx). */
    fprintf(stderr, "cage radius %.2f\n", cageR*vsz);
    
    double plateF = 0.25*plateT;  /* Fillet radius of top and bottom plate edges in {X} direction. */
    double plateR = cageR/sqrt(2) + 2*rodR + plateF; /* Half-extent of plate in {X} and {Y}. */
    double plateS = 2*rodR;       /* Rounding radius of vertical edges of plate. */

    auto double fuzzy_rod(r3_t *p);
      /* Indicator function for the rod. */
       
    auto double fuzzy_plate(r3_t *p);
      /* Indicator function for the plate. */
       
    int N = 4; /* Number of rods. */
    double a0 = M_PI/4; /* Azimuth of initial rod. */
    int i;
    for (i = 0; i < N; i++)
      { double ai = a0 + 2.0*M_PI*((double)i)/((double)N);

        /* Position and orientation of rod: */
        r3_path_state_t state;
        state.p.c[0] = ctr->c[0] + cageR*cos(ai);
        state.p.c[1] = ctr->c[1] + cageR*sin(ai);
        state.p.c[2] = rdmidZ;
        r3x3_ident(&(state.M));
       
        voxm_splat_object(a, fuzzy_rod, &state, hypot(rodH, rodR) + fuzzR, FALSE);
      }
    
    for (int dir = -1; dir <= +1; dir += 2)
      { /* Splat the plate at end {dir}: */
        double plateZ = (dir < 0 ? rdbotZ : rdtopZ) + dir*plateT/2; /* {Z} of plate center. */

        /* Position and orientation of plate: */
        r3_path_state_t state;
        state.p.c[0] = ctr->c[0];
        state.p.c[1] = ctr->c[1];
        state.p.c[2] = plateZ;
        r3x3_ident(&(state.M));
       
        voxm_splat_object(a, fuzzy_plate, &state, hypot(sqrt(2)*plateR, plateT) + fuzzR, FALSE);
      }
    
    return;
      
    double fuzzy_rod(r3_t *p)
      { return voxm_obj_rod(p, rodH, rodR, rodF, fuzzR); }
  
    double fuzzy_plate(r3_t *p)
      { return voxm_obj_rounded_box(p, plateR, plateR, plateT/2, plateS, plateF, fuzzR); }
  
  }

void mtv_splat_tubes
  ( ppv_array_t *a, 
    r3_t *ctr,
    double tbbotZ, 
    double tbtopZ, 
    double helixR,
    double bfR,    
    double inR,
    double tubeT,
    double fuzzR,
    mtv_options_t *o,
    bool_t sub
  )   
  {
    double otR = inR + tubeT;    /* Outer tube radius (vx). */

    /* Stage count and angular spans: */
    int32_t stageN = o->numStages;           /* Total number of stages (backflow loops). */
    double mcA = 2*M_PI/o->stagesPerTurn; /* Nominal angular extent of each stage (rad). */
    double bfD = o->loopExtent*mcA;       /* Angular extent of each backflow channel (rad). */

    /* The helix should make 1 full turn even if there is only 1 stage: */
    double helixN = o->helixTurns;   /* Number of turns of the helical part of the main channel. */
    
    double beW = o->loopTwist*M_PI/180;  /* Torsion angle of backflow channel at end of the turn (rad). */
    double bmW = beW/2;   /* Torsion angle of backflow channel at beginning of the turn (rad). */

    r3_path_state_t S0, S1;
    mtv_valve_helical_tubing_make
      ( a, ctr, tbbotZ, tbtopZ, helixN, helixR, 
        stageN, mcA, bfD, bfR, bmW, beW,
        inR, otR, fuzzR, sub,
        &S0, &S1
      );
  }

void mtv_tomogram_write(char *outFile, ppv_array_t *a)
  {
    FILE *wr = open_write(outFile, TRUE);

    bool_t plain = FALSE;
    ppv_write_array(wr, a, plain);
    
    fclose(wr); 
  }

mtv_options_t *mtv_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: */
    mtv_options_t *o = (mtv_options_t *)malloc(sizeof(mtv_options_t)); 
    
    /* Parse keyword parameters: */
    
    argparser_get_keyword(pp, "-voxelSize");
    o->voxelSize = argparser_get_next_double(pp, mtv_voxelSize_MIN, mtv_voxelSize_MAX);

     /* Array size: */
    double objSize_MIN = mtv_arraySize_MIN*o->voxelSize;
    double objSize_MAX = fmin(mtv_objectSize_MAX, mtv_arraySize_MAX*o->voxelSize);
    argparser_get_keyword(pp, "-objectSize");
    int k;
    for (k = 0; k < 3; k++)
      { o->objectSize.c[k] = argparser_get_next_double(pp, objSize_MIN, objSize_MAX); }
    
    argparser_get_keyword(pp, "-tubeDiameter");
    o->tubeDiameter = argparser_get_next_double(pp, mtv_tubeDiameter_MIN, mtv_tubeDiameter_MAX);

    argparser_get_keyword(pp, "-helixDiameter");
    o->helixDiameter = argparser_get_next_double(pp, mtv_helixDiameter_MIN, mtv_helixDiameter_MAX);

    argparser_get_keyword(pp, "-helixTurns");
    o->helixTurns = argparser_get_next_double(pp, 0.50, 100.0);

    double loopRadius_MIN = 2.0 + o->tubeDiameter/2;
    double loopRadius_MAX = mtv_helixDiameter_MAX/2;
    argparser_get_keyword(pp, "-loopRadius");
    o->loopRadius = argparser_get_next_double(pp, loopRadius_MIN, loopRadius_MAX);

    argparser_get_keyword(pp, "-numStages");
    o->numStages = (int32_t)argparser_get_next_int(pp, 0, 200);

    argparser_get_keyword(pp, "-stagesPerTurn");
    o->stagesPerTurn = argparser_get_next_double(pp, 1.0, 20.0);

    if (argparser_keyword_present(pp, "-loopExtent"))
      { o->loopExtent = argparser_get_next_double(pp, 0.10, 20.0); }
    else
      { o->loopExtent = mtv_loopExtent_DEFAULT; }

    if (argparser_keyword_present(pp, "-loopTwist"))
      { o->loopTwist = argparser_get_next_double(pp, -180.0, +180.0); }
    else
      { o->loopTwist = 0.0; }

    if (argparser_keyword_present(pp, "-cups"))
      { o->cups_bot = argparser_get_next_bool(pp);
        o->cups_top = argparser_get_next_bool(pp);
      }
    else
      { o->cups_bot = FALSE; o->cups_top = FALSE; }

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

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