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.
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:
GeoColorsPtr col = GeoColorsPtr::dcast(geo->getColors()); if(col == NullFC) { FWARNING(("Downcast failed!\n")); return; } beginEditCP(col, Geometry::ColorsFieldMask); col->push_back(Color3f(1, 0, 1)); ... endEditCP(col, Geometry::ColorsFieldMask);
GeoColors3ubPtr col = GeoColors3ubPtr::dcast(geo->getColors()); if(col == NullFC) { FWARNING(("Downcast failed!\n")); return; } MFColor3ub *c = col->getFieldPtr(); beginEditCP(col, Geometry::ColorsFieldMask); c->push_back(Color3ub(255, 0, 255)); ... endEditCP(col, Geometry::ColorsFieldMask);
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.
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.
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.
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.
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.
Dev: We definitely need a Teapot here... :)
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.
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.
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.
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 );
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.
1.4.3