# Triangle Consolidator 1.1

## Introduction

I imagine most people would only have come looking for TC if they knew what they wanted in the first place. The programmers' guide that you probably want is in the next section. The following is just a little purpose and history.

TC, or "triangle consolidator", constructs triangle strips or triangle fans from independent triangle data. The geometric transformation of 3D points onto a 2D screen involves some heavy math, and you can realize a performance benefit by reusing the results of that math where possible. Each new triangle strip or fan is constructed from one of the edges of the previous triangle and a new vertex.

You can achieve 3x performance minus the cost of transforming the first two vertices per strip. Thus it's important that strips and fans are made as long as possible (fewer strips and fans). Some architectures have a maximum preferred strip length, after which the engine may stall.

I wrote meshifier in 1995 because Sharon Clay at Silicon Graphics asked me how I might construct strips from a lattice of triangles as an interview question. It has some fundamental architectural limitations. Some, like an error in the reset code and being able to specify that triangles should not be output in reverse order, have been fixed by Neal Tringham and are incorporated in the new version available from his web page. Others, such as the limitation on numbers of vertices and connectivity, are harder to repair.

In "TC" I've attempted to fix those old errors. TC attempts to consolidate triangles limited only by the limits of the machine' available memory and speed. I have changed the API, using OpenGL as a loose model (although OpenGL arguably has its shape because it provides a hardware abstraction layer), so that more algorithmic abstractions and optimizations are possible. I have tried to build in features which are useful to modelers and offline optimization tools, such as whether or not to honor vertex ordering. I have also spent a few days trying to test the code nominally and attempt to have some confidence that even its low-memory behavior is predictable.

I hope you find it useful. ---Brad Grantham

## Triangle Consolidator API

TC is meant to be used in an offline modeling process. It is probably too slow to be used to optimize data per frame for the purpose of interactive display. It also does not provide any facility (yet) for merging spacial coordinates together to form an indexed vertex list. TC only operates on the indices of indexed triangles. Maintaining the actual vertex array (containing the traditional v3f, n3f, etc) is up to the application. I perceive this is useful, however, in that TC needs no knowledge of how vertex data is stored; it only needs to know connectivity information. An application program creates an ACTCData structure and then repeats a cycle of optionally setting flags, inputting triangles, and then receiving the output primitives.

### Language and Namespace

TC 1.1 is provided with a C language interface. Mostly this is because many people (including me) still prefer to program in C and I don't want to force people to write C++. TC is also implemented in C, but I don't think this is that important, since you could implement the library internals in C++ and still export a C API.

The namespace of all functions and identifiers in TC is protected using the initials of my software organization Applied Conjecture and `TC`. All functions in TC are prefixed with `actc`. All symbolic constants and `#defines` are prefixed with `ACTC_`. All exported data structures are prefixed with `ACTC`.

### Initialization and Errors

An `ACTCData` consolidator object is created using the following code. This structure contains state data about optional parameters and triangle connectivity. `actcNew` returns either an initialized `ACTCData` object if successful or NULL if memory cannot be allocated.

        #include <ac/tc.h>

        ACTCData *tc;

        tc = actcNew();
        if(tc == NULL) {
    	/* memory allocation failed */
    	/* print error here and exit or whatever */
        }

Any errors encountered during execution are typically both returned as results from a TC function and stored in the structure. The error can be retrieved using `actcGetError`. Here is an example of adding a triangle and checking the error.

        actcAddTriangle(tc, v1, v2, v3);
        if((e = actcGetError(tc)) != ACTC_NO_ERROR) {
    	/* something bad happened */
    	/* print error and exit or whatever */
        }

A more compact representation may not use `actcGetError` at all, since many functions return either the error or `ACTC_NO_ERROR`.

        if((e = actcAddTriangle(tc, v1, v2, v3)) != ACTC_NO_ERROR) {
    	/* something bad happened */
    	/* print error and exit or whatever */
        }

Since the error is reset in the structure by `actcGetError`, it makes sense to go with either one style or the other, to avoid being confused when a later return from `actcGetError` returns an error which has already been handled. Errors are represented by negative integers, so some functions may return non-error information as a positive integer. Slightly smarter applications may try to detect some failure conditions and proceed after some kind of repair.

        actcAddTriangle(tc, v1, v2, v3);
        e = actcGetError(tc);
        if(e == ACTC_ALLOC_FAILED) {
    	/* couldn't alloc memory using one of the malloc(3) family */
    	/* application can free unused memory and try again */
        } else if(e != ACTC_NO_ERROR) {
    	/* something bad happened other than malloc failure */
    	/* print error and exit or whatever */
        }

For simplicity, examples in the remainder of the programmers' guide will not check for errors but will mention where errors may occur. Several functions can result in the error ACTC_DATABASE_CORRUPT but are not noted here; that particular error return indicates corruption of ACTC's internal data structures and really requires debugging (as described below).

### Parameters

There are several options that may be set in the TC structure. For example, whether TC honors triangle vertex ordering is controlled by the `ACTC_OUT_HONOR_WINDING` symbolic constant. These values are set by `actcParami` and retrieved by `actcGetParami`.

        int isHonoringWinding;

        actcParami(tc, ACTC_OUT_HONOR_WINDING, ACTC_TRUE);
        actcGetParami(tc, ACTC_OUT_HONOR_WINDING, &isHonoringWinding);

`actcGetParami` accepts two additional symbolic constants which may not be set by the application. `ACTC_MAJOR_VERSION` and `ACTC_MINOR_VERSION` return the major version number and minor version number, respectively.

Parameters are differentiated by the direction of the data they control. `OUT` parameters control how TC hands its consolidated primitives back to you. `IN` parameters tell TC the limits you intend to guarantee on your input data, which may used for optimization purposes. `ALG` parameters directly affect the internal algorithms used by TC. Some parameters require the extended range of `unsigned int`, and they can be set and queried by `actcParamu` and `actcGetParamu`, but both sets of `Param...` functions take the same symbolic constants and convert to-and-from the range of their parameter or return variable. Here are the parameters available in TC 1.1:

Enumerant | Data type | Meaning | Prefer Paramu
--- | --- | --- | ---
ACTC_OUT_MIN_FAN_VERTS | integer | Minimum number of vertices returnable in a triangle fan | No
ACTC_OUT_HONOR_WINDING | boolean | Whether triangles may be output in reverse vertex order | No
ACTC_OUT_MAX_PRIM_VERTS | integer | Maximum number of vertices to output in a primitive | No
ACTC_IN_MIN_VERT | integer | Minimum vertex index | Yes
ACTC_IN_MAX_VERT | integer | Maximum vertex index | Yes
ACTC_IN_MAX_VERT_SHARING | integer | Maximum number of edges that will share a vertex | No
ACTC_IN_MAX_EDGE_SHARING | integer | Maximum number of triangles that will share an edge | No

If called during `INPUT` or `OUTPUT`, `actcParami` can result in `ACTC_DURING_INPUT`, or `ACTC_DURING_OUTPUT`, respectively. If called to set `ACTC_IN_MIN_VERT` or `ACTC_IN_MAX_VERT` so the vertex range is negative, or to set `ACTC_OUT_MAX_PRIM_VERTS` to less than 3, or to set a value associated with a symbolic constant ACTC does not know about, then the error `ACTC_INVALID_VALUE` will result.

### Input/Output

A TC structure is always in one of three modes; idle, inputting, or outputting. Some operations are valid only in some modes. For example, setting a parameter is an error unless the consolidator object is idle. Triangle data may only be entered in input mode. Primitives may only be fetched in output mode. Here's an example of how the modes work. Please keep in mind that this example assumes a particular primitive.

        actcParami(tc, ACTC_OUT_MAX_PRIM_VERTS, 14);
        actcBeginInput(tc);
        actcAddTriangle(tc, 1, 2, 3);
        actcEndInput(tc);
        actcBeginOutput(tc);
        prim = actcStartNextPrim(tc, &v1, &v2);
        actcGetNextVert(tc, &v3);
        actcEndOutput(tc);

        /* v1, v2, and v3 are now set to 1, 2, and 3. */

`actcEndInput` and `actcEndOutput` are intended as checkpoints for optimization, compaction, etc. Your application can call `actcGetIsDuringInput` and `actcGetIsDuringOutput` to determine if the TC object is currently ready in the input or output stages (or is otherwise idle). If `actcParami` is called during input or output, an error of either `ACTC_DURING_INPUT` or `ACTC_DURING_OUTPUT` results, respectively.

### Giving Triangles as Input

Each triangle is entered into the TC database using `actcAddTriangle`, and the possible results may be `ACTC_DURING_OUTPUT` if TC is in output mode, `ACTC_IDLE` if TC is not in input mode, or `ACTC_ALLOC_FAILED` if an internal data structure could not be allocated. In the last case, the database is not altered. An application could perform compaction on its own data and try `actcAddTriangle` again, or it could attempt to retrieve all the primitives currently available from the database. This would probably give worse results (more primitives, fewer vertices per primitive) than retrieving primitives from the complete database but may be better than getting no primitives at all.

         int triCount;
         int tris[][3];

         actcBeginInput(tc);
         for(i = 0; i < triCount; i++)
    	 actcAddTriangle(tc, tris[i][0], tris[i][1], tris[i][2]);
         actcEndInput(tc);

### Ranges of Various Input Data

Vertex indices can be any `unsigned int`, and so anywhere from 0 to `UINT_MAX`, inclusive. Internal data structures allow quantity `INT_MAX` vertices, edges, triangles, vertex valences, and edge valences to be input. With the data structures TC uses as of version 1.1, on most machines at the time of writing, a process' memory space will be exhausted before the range of any of these items is exhausted. As of version 1.1, TC has not been tested on an architecture with integers or pointers larger that 32 bits.

### Getting the Output Primitives

Strips and fans are retrieved one at a time from TC starting with `actcStartNextPrim`. The primitive type is returned, either `ACTC_PRIM_STRIP` or `ACTC_PRIM_FAN`. `actcStartNextPrim` may also return `ACTC_DATABASE_EMPTY` if all triangles input so far have been returned in primitives. `actcStartNextPrim` stores the first two vertices by reference, and `actcGetNextVert` provides each subsequent vertex. `actcGetNextVert` will return `ACTC_PRIM_COMPLETE` when their are no more vertices to return.

         int prim;
         int v1, v2;

         actcBeginOutput(tc);
         while((prim = actcStartNextPrim(tc, &v1, &v2) != ACTC_DATABASE_EMPTY) {
    	 /* start a primitive of type "prim" with v1 and v2 */
    	 while(actcGetNextVert(tc, &v3) != ACTC_PRIM_COMPLETE)
    	     /* continue primitive using v3 */
         }
         actcEndOutput(tc);

### One-Stop-Shopping (Convenience Function)

There is a single function called `actcTrianglesToPrimitives` which inputs triangles and extracts primitives, including attempting to recover from low-memory situations by first draining the primitive database, and then by cleaning up unused memory if draining is not sufficient. Since TC cannot know in advance how many primitives it can find (and thus how many vertices), the application will have to provide primitive storage large enough to hold the input data as independent triangles. The application may also tell `actcTrianglesToPrimitives` how many triangles to input to TC before draining the database. The following example shows how an application might call `actcTrianglesToPrimitives`; here the application has provided `INT_MAX` as the maximum batch size in triangles, which effectively means input all the triangles first before outputting.

        int triangleCount;
        int triangles[][3];
        int *primLengths;
        int *primVerts;
        int *primCount;

        /* get triangleCount and triangles from somewhere */
        primLengths = (int *)malloc(sizeof(int) * triangleCount);
        primTypes = (int *)malloc(sizeof(int) * triangleCount);
        primVerts = (int *)malloc(sizeof(int) * 3 * triangleCount);
        primCount = actcTrianglesToPrimitives(tc, triangleCount, triangles,
    	primTypes, primLengths, primVerts, INT_MAX);
        if(primCount < 0) {
    	/* something bad happened */
    	/* print error and exit or whatever */
        }

`actcTrianglesToPrimitives` calls `actc{Begin,End}{Input,Output}`, `actcAddTriangle`, `actcGetNext{Prim,Vertex}`, and `actcMakeEmpty`. It returns any unrecoverable error it may receive from these functions, or it will return the total number of strips and fans it finds. The types and length in vertices of all primitives are stored in primTypes and primLengths, and then the vertices for each primitive in turn are stored sequentially in primVerts.

### Memory Usage Statistics

Call `actcGetMemoryAllocation` to make TC search how much memory is being used in data structures. This may be an expensive operation in TC 1.1 in that it searches the allocated data structures.

### Dumping Internal State

See below for information on compiled TC so that it automatically dumps debugging information on an internal error. If you believe you are getting incorrect results from TC, your application can call `actcDumpState` to print out the internal state of a TC object (in no particular format). In order for me to help you debug a problem, it would be useful to print out the TC version number, the triangles you input to TC (in the order you input them), and the output of `actcDumpState` at the beginning and end of your output phase. `actcDumpState` has no return value.

### Cleaning Up

To stop input or output with TC and empty the database for a particular TC object, use `actcMakeEmpty`; the TC object will be reset to idle, contain no triangles, and will be ready for another set of input triangles. `actcMakeEmpty` returns any error that occurs during cleanup, but for the 1.1 release it can only return `ACTC_NO_ERROR`.

To free the memory associated with a TC object, call `actcDelete` with the pointer to the object; this function effectively calls `actcMakeEmpty` and then frees the TC internal data. Further use of that TC object pointer will have undefined results but will probably crash your application.

## Specific Solutions

### Producing Only Strips

To produce only triangle strips, set `ACTC_OUT_MIN_FAN_VERTS` to INT_MAX. Technically, a very large triangle fan could still be formed, but when someone creates a two billion vertex triangle fan with TC, I will consider it a success rather than a failure.

        actcParami(tc, ACTC_OUT_MIN_FAN_VERTS, INT_MAX);

### Producing Limited-Length Primitives

I have heard that Silicon Graphics' RealityEngine graphics and derivatives have an optimium strip size of 12 vertices. Set `ACTC_OUT_MAX_PRIM_VERTS` to set the maximum number of vertices to produce per primitive.

        actcParami(tc, ACTC_OUT_MAX_PRIM_VERTS, 12);

### Ignoring Triangle Winding

If your triangle set does not have a preferred triangle winding, set `ACTC_OUT_HONOR_WINDING` to FALSE or 0. This has the effect of allowing a triangle to be included in a primitive even if its vertices would be drawn in reverse order. Note that every odd triangle in a strip (counting from 0) will normally be wound backwards if winding is honored.

        actcParami(tc, ACTC_OUT_HONOR_WINDING, ACTC_TRUE);

## TC SDK

### Building TC

My preference for using this module is to compile tc into a `.a` library. The makefile provided by default has targets for the library and the sample programs:

        make libactc.a

To turn on some simple debugging assistance, compile TC with the `DEBUG` preprocessor symbol defined (`-DDEBUG` for most UNIX compilers). Error conditions should print the reason and the function in which the error occurred. Any function finding an unexpected inconsistency in data structures (like performing a table search and not finding an element which must be there) will call `abort()` and presumably dump core. I wouldn't expect any ordinary application developer to be able to debug TC from a core file, of course. If `INFO` is also defined, then the `abort()` call will be preceded by a dump of the internal data structures in tc. If you would like me to debug TC based on your input, it would help me to have this output as well as the set of triangles originally input to TC.

### Sample Programs and Tests

The `actctest` program makes sure the basic TC functions work. If it prints anything when compiled with actc.c without `DEBUG` and `INFO`, then something bad happened.

The `actctest2` program repeatedly inputs random data into TC and makes sure it gets the same thing out as it received. You can specify settings for any of the `OUT` parameters, and change the seed that `actctest2` uses to initialize the random number generator.

The `actcsample` program takes in a particular word-based format containing vertices and triangles, and converts that data into strips and fans. It passes the vertex list through verbatim. Someone could use this either as an element in a tool pipeline or as the basis for a new offline or interactive stripping tool. Here's what the input format looks like:

        [x y z nx ny nz r g b]
        [x y z nx ny nz r g b]
        [...]
        end
        triangle v1 v2 v3 end
        [triangle v1 v2 v3 end]
        [...]
        end

In this example, x, y, and z are the vertex coordinates, nx, ny, and nz are the vertex normals, and r, g, and b are the vertex colors. The set of vertices are terminated by the word `end`. Vertices are optional and are only passed straight through. For each triangle, v1, v2, and v3 are the indices of each vertex in the triangle. Each triangle is terminated by the word `end`, and the list of triangles is terminated by `end`. The output format is similar, except that the `triangle` section is replaced by a list of primitives. Here's what the output format looks like:

        [x y z nx ny nz r g b]
        [x y z nx ny nz r g b]
        [...]
        end
        strip v1 v2 v3 [v4 [v5 [...]]] end
        fan v1 v2 v3 [v4 [v5 [...]]] end
        [...]
        end

Running

### Portability

TC should be pretty portable, for the most part. I placed my "timer" snippet in `actctest2`, and it will need tweaking on anything but Linux, IRIX, and Windows 98. If you'd like to submit a patch to "timer" for another operating system, I will place it on my web page and incorporate it into TC.

## Miscellaneous

### Reliability

I've spent some time testing TC against some strange triangle sets. I've gone so far as to create random triangle data (random numbers of triangles with random ranges of vertices), put it in to TC, and check to make sure the same triangles come out as went in. I've even gone as far as to artificially reduce the amount of memory available to 8M, under which TC seems to reliably consolidate triangles, but the data set may degenerate under such conditions to being exactly the input data.

### Runtime Performance

TC 1.1 looks like it can consolidate somewhere around one hundred thousand triangles per second on a 400 MHz Pentium III for data sets in the thousands of vertices, and somewhere around thirty thousand triangles per second for data sets around three hundred thousand vertices. I suspect this could be improved only a little bit more with some clever avoidance of searching the vertex-to-edge mapping. Most of the data structures internal to TC are constant-time operations, especially if the vertex range is small.

I have tested TC's performance against an optimized meshifier modified by Randall Frank at Lawrence Livermore National Labs and meshifier appears to run about twice as fast, probably because it doesn't check for errors internally very well and makes some assumptions about vertex and edge connectivity. Perhaps TC could approach that performance level if the internal consistency checks were made to compile conditionally.

### Stripping Performance

TC uses a least-connected-vertex approach to making triangle strips, under the assumption that such vertices will be at the corners of patches of connected triangles, and the stripification algorithm will hopefully peel triangle strips off the outside of the patch first, in order to avoid fragmenting the triangle set. This is the same approach Neal Tringham's improved meshifier used. I have yet to test TC's vertices-per-strip performance against other triangle strippers, but it should do at least as well as meshifier.

The algorithm attempts to remove all the triangle fans first. I've found that this works for fewer than half of the simple data files I've tried. For 1.1 the default value associated with `ACTC_OUT_MIN_FAN_VERTS` is set to INT_MAX. Other good values appear to be near 8 and 16. You'll have to try it yourself to see how it works with your data.

One could conceivably implement simulated annealing with TC. An application would repeatedly calculate solutions to triangle sets with TC, swapping the order of some number of triangles, each time choosing the better solution. As time goes on, the number of triangles rotated could be reduced. Simulated annealing will typically give a pretty good solution while allowing the application to choose how much time it cares to spend to find a solution.

Revision Control Information: $Header: /home/grantham/cvsroot/projects/modules/tc/manual.html,v 1.15 2000/11/22 20:18:26 grantham Exp $