Most objects in a large model are made of many parametric surfaces. The OpenGL Optimizer classes that describe the connectivity of parametric surfaces, that is, their topology, allow you to “stitch” surfaces together by defining shared boundary curves, and to propagate surface contact information.
The main purpose for shared-boundary information is to generate tessellations of adjacent surfaces that are consistent, that is, no cracks develop between any pair of rendered surfaces. Tessellations are discrete approximations of surfaces in terms of renderable geometric primitives, typically triangles (see Chapter 11, “Rendering Higher-Order Primitives: Tessellators”).
These topics are covered in this chapter:
The topology classes provide definitions of boundary curves shared by adjacent parametric surfaces. Discrete versions of these curves are used by tessellators to prevent cracks. A rendered image can have artificial cracks due to the following:
Propagating surface contact information is useful for other tasks, such as
Maintaining consistent normal vectors for adjacent surfaces
Deforming a surface and consistently deform an adjacent surface
Determining whether an edge of a surface is in fact a shared boundary
Creating a mirror image of a compound surface (you can use topological information to reorient the surface)
The class opTopo holds data that indicates whether, and how, two opParaSurfaces are in contact. You can create several opTopos for a particular scene: for example, one each for subassemblies. A static member of opTopo lists all the opTopos that you create.
opTopo maintains lists of surfaces and boundaries (opBoundarys) that are shared by an arbitrary number of surfaces. Figure 10-1 illustrates how these data structures define relations between opParaSurfaces.
When an edge has been tessellated, the associated opBoundary holds a discrete version of the curve. This discrete version is needed for consistent tessellations because it specifies one set of boundary vertices for tessellating all the surfaces that share the boundary. The role of opBoundary in determining a consistent tessellation is illustrated in Figure 10-2.
The classes opTopo and opBoundary are examples of b-reps, which identify objects in terms of their bounding objects. opBoundary is also winged data structures, a particular form of b-rep. For more information on these structures, see the book Computer Graphics: Principles and Practice listed in “Recommended Background Reading”.
Given a set of opParaSurfaces in a scene graph, there are several ways to develop a set of shared vertices to be held in opBoundarys. The following sections describe the topology construction strategies (beyond the low-fidelity alternative of ignoring topology):
As each surface is tessellated during a traversal, the tessellator checks for previously tessellated adjacent surfaces, uses existing vertices when it can, and adds necessary data to topology data structures.
Although OpenGL Optimizer's incremental topology building tools attempt to avoid cracks, they can, in principle, appear: When a surface is added, a new junction on the boundary of an existing, tessellated surface may occur and the junction point may not be in the existing tessellation. The tessellation of the added surface introduces the junction point, necessarily at a finite distance from the existing tessellation, and a crack appears between the newly and previously tessellated surfaces.
Topology built with two passes is very clean; unlike a single-pass build, in principle no cracks due to unforeseen junctions can occur. The added cost of performing a two-traversal build is slight; it is the recommended way to build topology and perform tessellations if you want high-quality images. When building topology in two traversals, the following steps occur:
Connectivity of all surfaces is calculated during a topology building traversal of the scene graph, before a tessellation traversal.
The surfaces in the scene are tessellated during a second traversal.
You can explicitly accumulate a list of surfaces for which to build topology and then tessellate the surfaces. The result is clean tessellations of the surfaces on the list. Cracks may appear if an adjacent surface was not included in the list.
If you have a set of surfaces for which you know connectivity, you can explicitly develop the appropriate topological data structures and develop consistent tessellations.
The presence of cracks will depend on how good your input trim curves are. If three surfaces meet at a junction point that is not the shared endpoint of trim curves, a crack may appear.
Table 10-1 lists the methods required for each of the topology building strategies. See “Base Class opTessellateAction” for more information about the tessellation methods listed.
Table 10-1. Topology Building Methods
You can add topological information to an existing set of connected, higher-order surfaces in a file—for example NURBS in an .iv file—and save the information for future, crack-free surface rendering. As a result, you don't have to repeat the topology build. The method opGenLoader::load() reads the topological information in a .csb file. See “Saving and Loading Scene-Graph Files”.
Before you save the scene graph data, you can also add tessellations that use the topology to give crack-free images (see Chapter 11, “Rendering Higher-Order Primitives: Tessellators”).
The demonstration program opoptimize illustrates how to perform these steps (see , “Scene Graph Tuning With the opoptimize Application” and /usr/share/Optimizer/apps/sample/opoptimize).
Table 10-2 shows three possible file conversions that you can apply to .iv or .csb files that contain reps but no topology or tessellation; they are listed with example opoptimize command lines.
Table 10-2. Adding Topology and Tessellations to .iv and .csb Files
If you perform conversion, you may have files with or without tessellations. Depending on the type of file you read, use one of the command lines in Table 10-3.
Table 10-3. Reading .csb Files: With and Without Tessellations
To read a .csb file and perform tessellation (without having to build topology): |
| opoptimize surTopo.csb -ctol tessTol |
To read a .csb file that already has tessellations |
| opoptimize surTopoTess.csb -tess no |
| Note: If you attempt to load a tessellated surface, no additional tessellation is performed. |
To delete the tessellation date, use the method clearTessellation().
The following are the main methods in the class:
class opTopo : public csNode
{
public:
// Creating and destroying
opTopo( opReal tol = 1.0e-3,
opLengthUnits u = meter,
int sizeEstimate = 1024 );
~opTopo();
// Accessor functions
void setDistanceTol( opReal tol, opLengthUnits u )
opReal getDistanceTol( )
opParaSurface* getSurface( int i );
int getSurfaceCount( );
opBoundary* getBoundary( int i );
int getBoundaryCount( );
int getSolidCount()
opSolid* getSolid( int i )
//Adding topological elements
int addSurface( opParaSurface *sur );
int addBoundary( opBoundary *bnd );
//Topology construction
void buildTopology();
void buildTopologyTraverse(csNode *n);
int buildSolids();
};
|
| buildSolids() | Collects connected surfaces in the opTopo into opSolids (see “Collecting Connected Surfaces: opSolid”. | |
| buildTopology() |
| |
| buildTopologyTraverse() |
| |
| opTopo(tol,u,sizeEstimate) |
tol specifies a tolerance for calculating when points are close enough together to be considered the same. Default is 1 millimeter. u specifies the system of units for tol. Default is meters. sizeEstimate specifies an estimate of the number of surfaces whose topology needs to be maintained. |
The static member topology is an array of all topologies that have been created.
The opBoundary class is an element in the list of boundaries are shared by parametric surfaces that is maintained by opTopo. An opBoundary holds a curve that represents a common boundary, and points to adjoining surfaces. Notice that an opBoundary can include any number of surfaces that share a particular curve as a boundary, so it can represent the intersection of several surfaces and allow you to describe a non-manifold surface structure. An opBoundary can also hold just one surface, and thus represent a free edge.
The opBoundary class holds an opDisCurve3d xyzBoundary, which is derived from a tessellation, to store a discrete version of a shared boundary. The unique discrete version guarantees that tessellations of adjoining surfaces share the same vertices along the boundary and so prevents the development of cracks.
In addition to information identifying each surface, opBoundary stores the index used by each opParaSurface to identify the trim curve that defines the shared boundary. Because a boundary may consist of several trim curves, more than one trim curve, and therefore more than one opBoundary, can define a geometric boundary between two surfaces.
If you have an opParaSurface and want to identify adjacent surfaces, you have two options. The simplest is to find the opSolid that holds the surface, using the opParaSurface member _solid_id. At a lower level, you can identify each opBoundary associated with the surface by using the boundary index that is stored in each of the surface's opEdge trim curves. The boundary index identifies opBoundary members in the opTopo list. From each member of the list, you can identify surfaces that share that boundary. See the section “Parametric Surfaces” in Chapter 9 for more information about opEdge.
The following are the main methods in the class:
class opBoundary
{
public:
opBoundary( );
~opBoundary( );
// Accessor functions
void addEdge( int i, opParaSurface &sur, int trimLoop, int trimCurve );
int getSurface( int i );
int getLoop( int i );
int getTrimCurve( int i );
int getWingCount();
int getBoundaryId();
// Copy
opBoundary* clone(csNode::CloneEnum what);
};
|
| opBoundary() | ||
| addEdge(i, sur, trimLoop, trimCurve) |
| |
| getSurface(i) | Returns the opTopo index of the opBoundary surface with index i. The other get*() functions return elements associated with the surface. See “Parametric Surfaces” for more details about the returned objects. | |
| xyzBoundary | Is a discrete representation of the boundary curve. Notice that the curve is not in the coordinate space of any of the surfaces but represents the boundary as a curve in three-dimensional space. This curve defines the set of vertices used in the tessellations of all surfaces that share this boundary. |
The set*() methods, which you can find in opBoundary.h, are mainly for use when reading topological data from a file. For example, they are used by the .csb loader in opGenLoader to create topological objects when reading a file (see “Saving and Loading Scene-Graph Files”).
To maintain consistent normals or propagate deformation information, organize connected opParaSurfaces in an opSolid. With an opSolid, you can collect connected surface patches in one object for convenient access and manipulation.
Despite the name of the class, the set of surfaces need not form a closed surface, that is the boundary of a volume. They can be a set of patches joined to form a surface, for example, you might generate a hood of a car from two opParaSurafaces that are mirror images of each other.
To create solids, collect them in an opTopo and then call opTopo::buildSolid() (see “Summary of Scene Graph Topology: opTopo”).