tube - create Geomview OFF tube around Geomview VECT core
tube -r <x> --closed --capped --equalize <files>
The goal of tube is to provide a convenient way to generate high-quality triangulations of tubes of variable radius around polygonal curves stored in the Geomview VECT format. The results are output in the Geomview OFF format. (If you don't use Geomview, these well-documented, human-readable text file formats are easy to translate into other 3d file formats.)
The main attraction of tube is a relatively sophisticated algorithm for framing the tube and, for closed curves, closing the frame around the tube without introducing a visible seam at the end of the tube. In principle, it should be impossible to tell where the core of a tube begins or ends (unless you use the color-handling features of tube to mark the spot yourself!). The tube code also triangulates tubes in an intelligent way, assigning more triangles when the radius is larger or where the edges of the underlying polygon are shorter.
Tube is designed to handle curves with many components (links) and curves which aren't closed as easily as it handles single closed curves. Colors can appear more than once with each instance applying to a different component of the input curve, making it easy to generate pictures of links where the different components are color-coded. Tube will process up to 100,000 input files at a time, making it convenient for use in animation.
Tube has many more options than those listed in the synopsis.
If we have a parametrized space curve C(s) = (x(s),y(s),z(s)), the normal plane to the curve at s is the plane through C(s) with normal vector C'(s) = (x'(s),y'(s),z'(s)). The tube of radius r(s) around C(s) in 3-space is the union of the circles of radius r(s) in these normal planes centered at C(s).
To construct a polygonal approximation to the tube, we start by choosing coordinate axes X(s) and Y(s) on each normal plane. Then the tube is the surface
V(s,t) = r(s) cos t X(s) + r(s) sin t Y(s) + C(s).
where t is an angle between 0 and 2 pi.
Geometers usually write T(s) = C'(s)/|C'(s)|, so we'll go ahead and follow this convention. Since the three vectors X(s), Y(s), and T(s) are supposed to form a right-handed orthonormal frame for each s, we know that Y(s) = T(s) x X(s). This means that we really only need to find X(s) to define the tube surface. (The little x stands for ``cross product'', or ``vector product''. If you are not familiar with this operation, http://mathworld.wolfram.com/CrossProduct.html has a good description.)
Tube approximates the smooth tube surface V(s,t) by a triangulated polyhedral surface. The approximating surface is built by replacing some of the circles in normal planes by rings of vertices and spanning adjacent rings of vertices by triangles.
How do we know which vertices connect to which? Once we put in the first triangle, everything else will follow (though there are some subtleties which the number of vertices in adjacent rings isn't the same. See the last subsection of this section for details.)
We solve the ``first triangle problem'' this way: If we pick a selection of s values labelled by integers i, then we will assume that vertices V(i,0) = r(i) X(i) + C(i) and V(i+1,0) = r(i+1) X(i+1) + C(i+1) are part of the same triangle. But this convention makes our choice of X(s) very important.
We can think of X(s) as a field of vectors winding around C(s). If X(s) twists quickly, the results will be mathematically correct, but visually unpleasant. For instance, the triangle joining X(i) and X(i+1) will be strongly sheared, since X(i) and X(i+1) are twisted far away from each other.
This means that a good choice of X(s) is one that twists as little as possible. Tube is based on the Bishop frame, which does not twist at all! To give the mathematical definition of the Bishop frame, we first define twist. In the discussion that follows, let <A,B> be the dot product of the vectors A and B. (See http://mathworld.wolfram.com/DotProduct.html if you aren't familiar with the dot product.)
The twist of X(s) around T(s) is just the component of the derivative of X(s) in the Y(s) direction:
twist = <X'(s),Y(s)>.
If we write X'(s) as a X(s) + b Y(s) + c T(s), then b = <X'(s),Y(s)> = twist. Let's work out a and c.
We might as well assume that the length of X(s) is constant. This means that (d/ds) <X(s),X(s)> = 0. But if we simplify the derivative on the left, this equation yields
0 = 2<X(s),X'(s)> = 2a,
so a = 0. To find c, we remember that X(s) is part of a basis for the plane with normal vector T(s). So <X(s),T(s)> = 0 for all s. If we differentiate this with respect to s, we get
c = <X'(s),T(s)> = -<X(s),T'(s)>.
These three equations mean that a unit length vector field X(s) which is always perpendicular to T(s) and has twist = 0 is the unique solution to the system of ordinary differential equations
X'(s) = -<X(s),T'(s)>T(s).
This frame X(s) is the Bishop frame! (See ``There is more than one way to frame a curve'', by Richard Bishop, in the American Mathematical Monthly (1975).)
We can define a version of the Bishop frame for polygons, too. Suppose that the original curve was just a collection of points C(i) with corresponding tangent vectors T(i). Then we can choose any X(0) and define the rest of the X(i) by the two-step process
X(i+1) = X(i) - <X(i),T(i+1)> T(i+1)/|X(i) - <X(i),T(i+1)> T(i+1)|.
It's easy to check that |X(i)| = 1, <X(i+1),T(i+1)> = 0 and <X(i+1) - X(i),Y(i+1)> = 0.
You may remember that the solution to a first order system of ordinary differential equations (like the one defining the Bishop frame) is completely determined by its initial conditions (in this case, our choice of X(0)). This presents a problem: if the curve C(s) is closed, how do we know that the Bishop frame is closed, too? Unfortunately, math won't save us here. For most curves, the Bishop frame just doesn't close.
For software designers, this is a wraparound problem: if we have n rings of vertices to connect, then ring n-1 must be joined to ring 0. But X(n-1) and X(0) might be far apart, creating a shear artifact on this band of triangles. Tube solves this problem in two ways. First, we notice that we don't actually have to join the vertex at X(n-1) to the vertex at X(0). In fact, we could search for the closest vertex on ring 0, and join that vertex to X(n-1). If there are K vertices on ring 0, this reduces the shear to at most 1/K of the circumfrence of the ring.
Unfortunately, this is still enough to be visible. So tube compensates further by distributing just enough twist over the entire Bishop framing X(s) to ensure that X(n-1) lines up perfectly with some vertex on ring 0. The result is entirely invisible.
Remember that our tube triangulation algorithm is based on building rings of vertices at each vertex C(i) of the polygonal curve, aligning these rings by pairing vertex V(i,0) in the ith ring with vertex V(i+1,0) in the i+1st ring, and bridging the rings by triangles. We now go into more detail about this bridging process.
We can think of triangle construction as a procedure of taking steps on each ring: when we step along ring i, the triangle we build has its base on ring i and it's tip on ring i+1, and when we step along i+1 things are reversed. If the rings have the same number of vertices, we can simply alternate steps on each ring. But if the rings have different numbers of vertices, one ring must step more often. This leaves us with a problem: how do we alternate steps as evenly as possible?
Luckily, this was one of the earlier solved problems in computer graphics: to draw a line at a given slope on a computer screen gridded with square pixels we must alternate discrete steps in the x and y directions as evenly as possible. This is handled by Bresenham's algorithm, and tube uses a slightly modified Bresenham code to connect rings of different numbers of vertices.
The main advantage of this technique is that tube can be sensitive to the varying lengths of edges in the original curve, so that if the original polygon has shorter edges in regions of high curvature, tube can smoothly assign more triangles to those portions of the tube. A nice side-effect of all this work is that we can handle variable-radius tubes in the same way, assigning more vertices (and hence more triangles) to rings of larger diameter.
tube uses the argtable library for command-line parsing, so the syntax of option handling should follow GNU conventions. Here are descriptions of the individual options. If there is more than one input file, the options given apply to each input file as if tube was run on that file alone.
If r, g, and b are not all given on the command line, the missing colors default to a value of 1.0. Colors may appear up to 256 times on the command line. If they appear more than once, tube applies them to the different components of the input curve, in order. This makes it easy to give different components of a link different colors.
The --stepratio (and --equalize) parameters allow the user to fine-tune this algorithm. Setting --stepratio=x multiplies the number of vertices on each ring chosen by about x (which can be any floating-point number). Large values of x create smoother tubes with more triangles, while small values of x create more polyhedral tubes with fewer triangles.
While tube sets the number of vertices in each ring according to its own algorithms, by default it always generates one ring of vertices for each vertex of the input polygon. The --equalize option allows tube to reparametrize the input polygon to get the best tube possible with the fewest number of vertices. The --equalize algorithm changes the core polygon slightly, but the difference should be invisible in most graphics applications.
The user can force every component of each polygonal input curve to close by setting the --closed flag. This will smoothly close any curve at all, including those originally very far from closure, but the results for such curves are somewhat unpredictable.
The rad argument controls the multiple of the overall radius to add to the tube radius at the end of flare. The default value is 2.0. By setting negative values for rad you can probably generate tapered ends, but you're on your own here.
The dist argument controls the fraction of the tube (by arclength) to flare. It is given as a number between 0 and 1. Flare distances apply to both ends of the tube, so setting dist > 0.5 means that the entire tube will be part of a flared region.
Multiple flares can be added to a tube to generate particular visual effects. If providing more than one set of options, remember that the total number of flares is the largest number of times one of these options occurs. If not every option is present that number of times, the missing values are filled in by defaults.
All flares apply to all open components of all input curves.
As for flares, pow controls the curvature of the bulge, with higher values generating more abrupt bulges, while lower values generate smoother bulges. Negative values for pow are forbidden, while values less than 1.0 may generate strange-looking tubes. The default is 3.0.
The parameter rad controls the multiple of the original tube radius to add to the original radius during a bulge. Setting rad < 0 might generate pinched tubes. The default value is 2.0.
The parameters start and end specify locations along the curve to start and end a bulge as fractions (between 0 and 1.0) of the arclength of the core curve. Multiple bulges may overlap, and for closed curves, bulges may wrap around the end of the tube (if start > end). The default is to bulge the middle third of the tube.
As with flares, multiple bulges can be created by repeating these options on the command line. If providing more than one set of options, remember that the total number of flares is the largest number of times one of these options occurs. If not every option is present that number of times, the missing values are filled in by defaults.
All bulge options apply to all components of all input files.
If the input is named file.vect, the output file will be file.tube.off.
Generate a tube of radius 1.0 around the file 3_1.vect:
tube -r 1.0 3_1.vect
the output will appear in 3_1.tube.off.
Generate tubes colored red, green, and blue around the components of the file borromean.vect:
tube -r 1.0 --red=1.0 --green=0.0 --blue=0.0 --red=0.0 --green=1.0 --blue=0.0 --red=0.0 --green=0.0 --blue 1.0 borromean.vect
the output will appear in borromean.tube.off.
Generate a tube with flared ends around the file fingerpuzzle.vect:
tube -r 1.0 --flare fingerpuzzle.vect
the output will appear in fingerpuzzle.tube.off.
Generate a very smooth tube around 3_1.vect:
tube -r 1.0 --stepratio=4 3_1.vect
the output will appear in 3_1.tube.off. This is the largest value of stepratio that is probably useful in practice.
Jason Cantarella
This program is covered by the GNU General Public License for free software.