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
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
.
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).
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.
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.
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);
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.
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);
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.
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.
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.
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.
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);
ACTC_OUT_MAX_PRIM_VERTS
to set the maximum number of vertices to produce
per primitive.
actcParami(tc, ACTC_OUT_MAX_PRIM_VERTS, 12);
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);
.a
library. The makefile provided by default has targets for the library and
the sample programs:
make libactc.aTo 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.
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] [...] endIn 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 [...] endRunning
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.
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.
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/web/actc/manual.html,v 1.5 2000/11/22 20:19:36 grantham Exp $