Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

Geometry

Geometries in most cases define what's being rendered. Note that Geometries don't necessarily have to be leaves of the tree, as due the Node/Core divison every Node keeping a osg::Geometry Corehas children anyway, which are used just as all other osg::Node's children. Geometry has to be flexible, to accommodate the needs of the application. Different data types for the data that defines the geometry are useful, as well as different indexing capabilities to reuse data as much as possible. On the other hand, it also has to be efficient to render. Flexibility and performance don't always go well together, thus, there are some simplifications to make.

Properties

OpenSG geometry is modeled closely following OpenGL. The data that make up the geometry are stored in separate arrays. Positions, Colors, Normals and Texture Coordinates all have their own arrays, (or osg::MField, to stay in OpenSG terminology). As OpenGL can handle a lot of different formats for the data, some of which might be more appropriate due to speed and memory consumption than others, depending on the application, OpenSG features different versions of this data, allowing pretty much all the variants that OpenGL can handle. To allow that with type safety and without having a separate geometry class for every possible combination the data fields are stored in separate field containers, a so called osg::GeoProperty. There are separate osg::GeoProperty for different attributes, and variants for different data types for each kind of osg::GeoProperty. The most prominent types are probably osg::GeoPositions3f for osg::Pnt3f positions, osg::GeoNormals3f for osg::Vec3f normals, osg::GeoColors3f for osg::Color3f colors and osg::GeoTexCoords2f for osg::Vec2f texture coordinates, but other variants are possible.

As properties only have a single field they can mimic that field by exposing parts of the standard osg::MField interface for their contents, so you can use a GeoProperty pretty much just like an osg::MField. One problem with the type variety is that writing functions that work on every type of property can become tedious, as you have to have a big switch for every kind of data that could arrive. To make that easier for every property there is defined generic format, e.g. for Positions the format is osg::Pnt3f. A property has a getValue()/setValue() interface for these generic types, i.e. every property, no matter in what format it stores the data, can be used as if it used the generic format. Of course, this is not as efficient as directly accessing the data, but if speed is not the highest priority or as a fall-back it's quite useful. And, finally, osg::GeoProperty features an interface for OpenGL vertex arrays, giving access to the data and the types involved, which is used for rendering.

In addition to the above-mentioned data there are some other osg::GeoProperty. OpenSG allows multiple primitive types per geometry, i.e. you can freely mix triangles, triangle strips and polygons in a single geometry node. The osg::GeoPTypes property defines the type of the primitives used. Right now, it only exists as a osg::GeoPTypesUI8 variant, but others may follow. The number of vertices per primitive is defined by another property, the osg::GeoPLengths property. This, too, only exists in a osg::GeoPLengthsUI32 variant right now.

Class Structure

The basic idea of the GeoProperty class structure is very simple. The actual implementation looks complicated, but is primarily so to simplify reuse of code and simplify extensions. If the ideas behind the structure are understood, the actual code doesn't look so bad any more.

All properties are derived from osg::Attachment, so they can directly be used as attachments and attached to an osg::AttachmentContainer.

There are two primary types of GeoProperties: osg::AbstractGeoProperty and osg::GeoProperty.

osg::AbstractGeoProperty describes the different kinds of attributes a Geometry can have: Positions, Colors, Normals, TexCoords, Indices, Types and Lengths. These are abstract, they have no specified data type and thus cannot be instantiated. They are the kinds of properties, not the actual properties. Use them in cases where you want to be able to use any actual type of data for a specific kind of property.

osg::GeoProperty describes the concrete versions of the properties. These are typed, can be instantiated, and these are the versions normally used by an application to set up their osg::Geometry node cores. All the concrete classes like osg::GeoPositions3f and osg::GeoTexCoords2f are typedefs of this class.

The actual interface to access the Properties has been factored out into separate classes, osg::GeoPropertyArrayInterface and osg::GeoPropertyInterface. Both of these are supported by all kinds and types of properties, the distinction is made for future extensions.

osg::GeoPropertyArrayInterface defines a general, type-independent interface to all kinds of properties. It is very similar to the interface used by OpenGL's Vertex Arrays, i.e. it is possible to access the type, dimensionality and stride of the data, as well as getting access to the base pointer. This interface is primarily used for rendering. Dev: The main reason for this interface is future extensibility to allow non-Property data to be used in osg::Geometry nodes, e.g. to use data that is stored in external libraries inside OpenSG.

osg::GeoPropertyInterface is the typed interface. Thus there are specific classes for every kind of property, which use the GenericType of the specific kind in their interface. This interface is the one that models the osg::MField interface, it has functions to add values to and change values of the property.

Dev:

Do we need a subValue here? DR

Hint!

Some rules of thumb:

Indexing

Using these properties it is possible to define geometry. Note that OpenSG inherits the constraints and specifications that concern geometry from OpenGL. Vertex orientation is counterclockwise when seen from the outside, and concave polygons are not supported.

geo_nonindexed.png

Non-Indexed Geometry

One additional advantage of separating properties from Geometry is the ability to share properties between geometry osg::NodeCore s. As geometries can only have one material right now that's useful for simplifying the handling of objects with multiple materials.

This simple geometry has one problem: there is no way to reuse vertex data. When a vertex is to be used multiple times, it has to be replicated, which can increase the amount of memory needed significantly. Thus, some sort of indexing to reuse vertices is needed. You can guess what's coming? Right, another property.

Indices are stored in the osg::GeoIndices property, which only exists in the osg::GeoIndicesUI32 variant right now. When indices are present the given lengths define how many indices are used to define the primitive, while that actual data is indexed by the indices.

geo_indexed.png

Indexed Geometry

Indexed geometry is very close to OpenGL, and probably the most often used type of geometry. It doesn't handle all the cases, though.

Sometimes vertices need different additional attributes, even though they have the same position. One example are discontinuities in texture coordinates, e.g. when texturing a simple cube. The edges of the cube don't necessarily use the same texture coordinate. To support that a single indexed geometry has to replicate the vertices.

To get around that you need multiple indices per vertex to index the different attributes. Adding an index for every attribute would blow up the geometry significantly and not necessarily make it easier to use. We decided to use another way: interleaved indices.

geo_multiindexed.png

Multi-Indexed Geometry

Interleaved indices require every vertex to hold multiple indices. Which index is used for what attribute is defined by a separate indexMapping field. The indexMapping field is a osg::UInt32 osg::MField. The possible values are bitwise combinations of the available attribute masks: osg::Geometry::MapPosition, osg::Geometry::MapNormal etc. The length of the indexMapping defines how many indices are used per vertex. If it's not set a single index for all available properties is used (or none at all).

In addition to the properties geometry keeps a osg::MaterialPtr to define the material that's used for rendering the geometry (see Materials) and a flag that activates caching the geometry in OpenGL display lists. As geometry rendering is not optimized very much right now that's the best way to get decent performance. Display lists are turned on by default.

Geometry Iterators

The osg::Geometry setup is very nice and flexible to define: you can mix different kinds of primitives in an object, you can have properties and different kinds and the indexing allows the reuse of some or all of the data.

From the other side of the fence things look different: if you want to walk over all triangles of a geometry to calculate the average triangle size or the surface area, or for calculating face normals or for whatever reason you have to take care of all the flexibility and be prepared for lots of different ways to define geometry.

To simplify that the concept of a geometry iterator has been introduced. A geometry iterator allows to iterate over a given geometry primitive by primitive, face by face (a face being a triangle or quad), or triangle by triangle.

All of them are used like STL iterators: the osg::Geometry has methods to return the first or last+1th iterator, and to step from one element to the next. They can also unify the different indexing variants: when using an iterator you can access the index value for each attribute of each vertex of the iterator separately. Or you can directly access the data that's behind the index in its generic form, which is probably the easiest way of accessing the data of the osg::Geometry.

Example: The following loop prints all the vertices and normals of all the triangles of a geometry:

for(it = geo->beginTriangles(); it != geo->endTriangles(); ++it)
{
    std::cout << "Triangle " << it.getIndex() << ":" << std::endl;
    std::cout << it.getPosition(0) << " " << it.getNormal(0) << std::endl;
    std::cout << it.getPosition(1) << " " << it.getNormal(1) << std::endl;
    std::cout << it.getPosition(2) << " " << it.getNormal(2) << std::endl;
}

If you're used to having a separate Face object that keeps all the data for a face, the Iterators pretty much mimic that behavior. The one thing you can't do using iterators is changing the data. To do that you have to use the Indices the Iterators give you and access the Properties directly. Be aware that the Iterators hide all data sharing, so manipulating data for a face the iterator gives you can influence an arbitrary set of other faces.

Primitive Iterator

The osg::PrimitiveIterator is the basic iterator that just iterates through the osg::GeoPTypes property and gives access to the primitive's data. It is useful to solve the index mapping complications and to get access to the generic data, but it's primarily a base class for the following two iterator types.

Face Iterator

The osg::FaceIterator only iterates over polygonal geometry and ignores points, lines and polygonal primitives with less than three vertices. It also splits the geometry into triangles or quads.

Triangle Iterator

The osg::TriangleIterator behaves like the osg::FaceIterator, but it also splits Quads into two triangles, thus it does an implicit triangulation. As OpenSG just like OpenGL doesn't support concave geometry that's not as hard as it sounds.

Line Iterator

The osg::LineIterator only iterates over line geometry and ignores points, polygonal primitives and line primitives with less than two vertices. It splits line strips and loops into single lines.

Edge Iterator

The osg::EdgeIterator (currently) only iterates over line geometry and ignores points, polygonal primitives and line primitives with less than two vertices like the osg::LineIterator does, but it leaves line strips and loops as they are. This iterator will make more sense in a future version, where it returns the edges of the other primitives as single lines or line loops as well.

Dev: For all polygonal primitives probably except GL_POLYGON itself the edges could be returned as single lines conceptually similar to the implicit triangulation of the osg::TriangleIterator. For a polygon it seems reasonable to me to reinterpret it as line loop. Shouldn't we overload getType() to return the interpretation?

The iterators can also be used to indicate a specific primitive/face/triangle/line. Each of these has an associated index that the iterator keeps and that can be accessed using getIndex(). A new iterator can be used to seek() a given primitive/face/triangle again and work on it. This is used for example in the osg::IntersectAction.

Simple Geometry

OpenSG does not have NodeCores for geometric primitives like spheres, cones, cylinders etc. Instead there are a number of utility functions that can create these objects. They can be created as a ready-to-use node and as a naked node core. In most cases we tried to mimic the VRML primitive semantics, so if you're familiar with VRML you will feel right at home.

Dev: We definitely need a Teapot here... :)

Plane

osg::makePlane creates a single subdivided quad.

Box

osg::makeBox creates a box around the origin with subdivided sides.

Cone

osg::makeCone create a cone at the origin.

Cylinder

osg::makeCylinder create a cylinder at the origin.

Torus

osg::makeTorus create a torus at the origin.

ConicalFrustum

osg::makeConicalFrustum creates a truncated cone at the origin.

Sphere

There are two ways to create a sphere. osg::makeSphere uses a icosahedron as a base and subdivides it. This gives a sphere with equilateral triangles, but they do not correspond to latitude or longitude, which makes it hard to get good texture mapping on it. As every subdivision step quadruples the number of triangles, it is also hard to control the complexity of these kinds of spheres.

osg::makeLatLongSphere on the other hand creates a sphere by simply usign a regular subdivision of latitude and longitude. This creates very small polygons near the poles, but is more amendable to texture mapping and gives finer control of the resoltuion of the sphere.

Extrusion Geometry

osg::makeExtrusion creates a pretty general extruded geometry. It works by sweeping a given cross section, which can be given clockwise or counterclockwise, across a spine. For every spine point an orientation and a scale factor are specified. The beginning and the end of the object can be closed by caps, but for the capping to work the cross section has to be convex. The resulting geometry can be refined as a subdivision surface (no idea which subdivision scheme is applied, anyone care who knows care to take a look?). Optionally normals and texture coordinates can be generated

Helper Functions

A number of helper functions can be used in conjunction with manipulating and optimizing geometry.

Normal Calculation

A common problem for self-created geometry or for geometry loaded from simple file formats are missing normals. Normals are needed for proper lighting, without them objects will either be black or uniformly colored.

Normals can be calculated either for every face or for every vertex.

Face normals, as calculated by osg::calcFaceNormals, are only unique for a given triangle or quad. The resulting object will look faceted, whcih may or may not be the desired effect. This will also work for striped or fanned models, as OpenSG doesn't have a per-face binding and uses multi-indexed per-vertex normals for this.

Vertex normals are calculated for every vertex and allow a shape to look smooth, as the lighting calculation is done using the vertex normals and interpolated across the surface. They can be calculated using two different methods.

osg::calcVertexNormals(GeometryPtr geo) will just average all the normals of the faces touching a vertex. It does not unify the vertices, i.e. it does not check if a vertex with given coordinates appears in the position property multiple times, the geometry has to be created correctly or be run thrugh osg::createSharedIndex.

The disadvantage of osg::calcVertexNormals(GeometryPtr geo) is its indiscriminative nature, it will average out all the edges in the object. The alternative is osg::calcVertexNormals(GeometryPtr geo, Real32 creaseAngle), which uses a crease angle criterion to define which edges to keep. Edges that have an angle larger than creaseAngle will not be averaged out. It won't always work for striped geometry. It will process it, but if a stripe point needs to be split because it has two normals, that won't be done. The same sharing caveat as given above applies.

Calculating vertex normals with a crease angle sounds simpler than it is, especially if the calculation should be independent of the triangulation of the object. Thus the algorithm is relatively expensive and should be avoided in a per-frame loop. There are some ideas to do the expesive calculations once and quickly reaverage the normals when needed. These have not been realized, if you need this or even better want to implement it, notify us at info@opensg.org.

Geometry Creation

Setting up all the objects needed to fully specify an OpenSG Geometry can be a bit tedious. So to simplify the process there are some functions that take data in other formats and create the corresponding OpenSG Geometry data.

Right now there is only one function to help with this, osg::setIndexFromVRMLData. It takes separate indices for the different attributes, as given in the VRML97 specification, together with the flags that influence the interpretation of these indices, and sets up the indices, lengths and types properties of the given geometry.

Geometry Optimization

OpenSG's Geometry structure is very flexible and pretty closed modeled on OpenGL. But not all of the Geometry data specification variants are similarly efficient to render. The functions in this group help optimizie different aspects of the Geometry.

osg::createOptimizedPrimitives takes a Geometry and tries to change it so that it can be rendered using the minimum number of vertex transformations. To do that it connects triangles to strips and fans (optionally). It does not change the actual property values, it just creates new indices, types and lengths. The algorithm realized here does not try a high-level optimization, instead it is optimized for speed. Due to its pseudo-random nature it can be run multiple times in the same time a more complex algorithm needs, allowing it to try different variants and keeping the best one found. Or, if execution time is a problem, it can be run only once and create a very quick result that is good, but not optimal.

osg::createSharedIndex tries to find identical elements in the Geometries Properties and remove the copies. It will not actually change the Property data, it will just change the indexing to only use one version of the data. This is a necessary preparation step to allow osg::createOptimizedPrimitives to identify the triangles it can connect to form stripes and fans.

osg::createSingleIndex resorts the Geometry's Property values to allow using a single index (in contrast to interleaved multi-indices) to represent the Geometry. To do that it might have to remap and copy Property values, as well as index values. While multi-indexing can be very efficient datawise, as as much of possible is shared, for rendering it is problematic. OpenGL doesn't know multi-indexing, thus for multi-indexed Geometry the more efficient OpenGL geometry specifiers like VertexArrays can't be used, which can have a significant impact on performance, especially for dynamic objects.

UInt32 calcPrimitiveCount ( GeometryPtr geo, UInt32 &triangle, UInt32 &line, UInt32 &point );

Normal Visualisation

For debugging it can be useful to actually see the normals of an object, as that allows making sure that the normals point in the expected direction and that normals are really identical and not just pretty close. Every normal is represented by a line of a user-defined length.

As OpenSG doesn't have an explicit face/vertex binding mode there are two different functions to create an object representing the vertex or face normals. The application should know whether face or vertex normals are used in the given geometry. In general it is safe to assume vertex normals are used.

osg::calcVertexNormalsGeo creates an object that shows the vertex normals of the given geometry, while osg::calcFaceNormalsGeo creates an object that shows the face normals.

Dev: We should add something more general here. Forcing the app to know is not nice. calcVertexNormalsGeo can already calc normals at the vertices and at the centers of the tris, but the decision when to use which and which normal to use is not clear.


Generated on Thu Aug 25 04:12:27 2005 for OpenSG by  doxygen 1.4.3