Chapter 22. Packaging Graphics with COLLADA

Thus far, you’ve seen 2D applications that access the frame buffer, 3D applications coded in OpenGL, and 3D games coded with Ogre3D. This chapter doesn’t create any new types of graphics or graphical applications, but shows how to package existing graphics using a powerful file format and object model. This is called the Collaborative Design Activity, or COLLADA.

Packaging graphics isn’t usually an interesting subject, and after all you’ve been through, it may seem like an anticlimax. But COLLADA is an incredibly new and exciting topic in the field of graphics, and given its wide adoption, it’s an important technology to become familiar with. When you know how to read and modify COLLADA files, you’ll be able to share your graphics with others and take the best advantage of COLLADA-compatible tools.

This chapter explains the COLLADA file format in detail and explains how to build applications that access these files. Before getting into the technical details, however, it’s important to come to grips with what COLLADA’ is and why it was created.

Introducing COLLADA

Chapter 21, “Building Games with Ogre3D,” explained how graphical objects in Ogre3D are defined with mesh, skeleton, and material files. The mesh file stores the object’s geometric data, the skeleton file stores its animation structure, and the material file stores its rendering parameters. These file formats are specific to Ogre3D.

The files created by proprietary tools contain similar information as Ogre3D files but have completely different formats. Each new format requires new tools for importing, exporting, and converting files to the formats used by other tools. The problem is clear: The more formats there are, the more difficult it is to communicate between them.

Rather than deal with the complexity, many game development houses build custom toolchains from scratch. This removes the need to convert between file formats but requires a great deal of time and effort. And when a new modeling tool or game engine reaches the market, there’s no way to incorporate it into their development process.

Many have recognized the need for a common medium for digitally created content (DCC). In 2003, Sony Computer Entertainment (SCE) discussed this need with a number of other graphic development companies, including Criterion Software and Vicarious Vision. The following year, SCE presented their results at the Association for Computing Machinery’s (ACM’s) Special Interest Group for Graphics and Interactive Techniques (SIGGRAPH).

What SCE demonstrated was a new method of storing and accessing graphic data. In their demonstration, they displayed the content of one vendor with the tools of another. SCE also showed how a single graphic could be portrayed on both the Xbox and PlayStation. The common file format was called the Collaborative Design Activity, or COLLADA.

COLLADA allows applications to access DCC generated by many different tools. Reading XML files can be time-intensive, so most tools won’t access COLLADA’s Digital Asset Exchange (DAE) files directly. Instead, COLLADA data can be placed in a runtime cache that applications can read and write to quickly.

The importance of COLLADA was recognized early on, and companies such as NVidia and Nokia joined the working group. The standard has been revised four times since 2003. The most recent version, 1.4, organizes data related to shading, textures, physics, and animation. COLLADA converters have been developed for many modeling tools including Maya, Blender, LightWave, Ogre3D, and 3ds Max. Google Earth, the globe visualization software, stores its graphical data in COLLADA format.

In addition to the DAE file format, SCE has created libraries whose functions can access and modify data contained in a COLLADA file. This document object model (DOM) makes it easy for applications to read, edit, and store information using the COLLADA format. This chapter examines both the file format and the associated DOM.

COLLADA’s Digital Asset Exchange (DAE) Format

The COLLADA format is detailed but straightforward. Like all XML-based documents, it consists of a root element that contains elements that may contain subelements. Elements are organized in a hierarchical structure and their information is delimited by tags (for instance, <TAG></TAG>). In addition to having parent and child elements, an element may also contain attributes: data defined within the element declaration.

If you followed the previous chapter’s discussion of Ogre3D files, you’ll find the COLLADA file structure easy to understand. You can think of a DAE file as a combination of the Ogre3D mesh, skeleton, and material files. Not only are the concepts similar, but in many cases the COLLADA names are identical to those used by Ogre3D.

All DAE files have the following characteristics:

  • The *.dae suffix, which stands for Digital Asset Exchange.

  • Data is stored as text and structured in XML.

  • XML data is contained in <COLLADA></COLLADA> tags and must have at least one asset element defined in <asset></asset> tags. This must be the first element under the top-level <COLLADA> tag.

In addition to the <asset> element, a DAE file may contain zero or more library elements that identify the graphic’s shape and appearance. For example, a file may have a <library_geometries> element to define its vertices and/or a <library_materials> element to identify properties such as reflectance. After the library elements, a file may contain additional elements such as <scene> and <extra>.

The <asset> Element

The <asset> element of the DAE file provides basic meta information about the content that follows: the title, creator, creation/modification time, version numbers, and associated comments. Table 22.1 lists the possible subelements inside the <asset></asset> tags. Required subelements are presented in bold.

Table 22.1. Asset Subelements

Subelement

Description

<contributor>

Name of the author, modeling tool, and other creation information

<created>

Date/time the document was created: YYYY-MM-DDTHH:MM:SSTZ

<modified>

Date/time the document was last modified: YYYY-MM-DDTHH:MM:SSTZ

<source_data>

URI that locates the source data/code for the created content

<unit>

Units of length used in the content and the relationship to the meter

<up_axis>

Identifies which vector is “up”: X_UP, Y_UP, or Z_UP

<revision>

Content version: major_num.minor_num.revision_num

<title>

Name of the overall project for which the content was created

<subject>

Identifies and describes the file’s graphical content

<keywords>

Words that identify the content in a search routine

<comments>

Further information about the content and project

The <contributor> element is optional, but is usually present to identify the content’s creator and the creation tool. This is the only element in Table 22.1 that contains further subelements, and these are given by the following:

  • <author>: Name of the developer or content creator

  • <authoring tool>: Name of the development tool used to build the content

  • <copyright>: Year and organization holding rights to the content

  • <source_data>: URI to the preconverted source data

  • <comments>: Additional information

None of these subelements are required.

The <created>, <modified>, and <unit> elements require explanation. The <created> and <modified> elements identify exactly when the content was created and last modified. The format is given by the ISO 8601 standard: YYYY-MM-DDTHH:NN:SSTZ.Y stands for year, M stands for month, D stands for day, H stands for hour, N stands for minute, S stands for second, and TZ stands for time zone. For example, if the DCC was created just before the turn of the millennium in Hollywood, the creation date could be identified with the following:

<created>1999-12-31T23:59:59U</created>

T separates the date from the time, and U denotes the standard time zone for the western United States.

The <unit> element contains attributes related to units of length. If you choose to measure your graphic in feet (1 foot = .3048 m), you’d express this with the following:

<unit name="foot" meter="0.3048" />

The following DAE file contains a complete <asset> subelement:

<?xml version="1.0" encoding="utf-8"?>
<COLLADA version="1.4.0"
   xmlns="http://www.collada.org/2005/11/COLLADASchema">
   <asset>
      <contributor>
         <author>Matthew Scarpino</author>
         <authoring_tool>By Hand</authoring_tool>
         <comments>COLLADA is cool.</comments>
      </contributor>
      <created>2008-03-25T14:21:29U</created>
      <modified>2008-03-25T21:51:29U</modified>
      <unit name="millimeter" meter="0.001"/>
      <title>Chapter22</title>
      <subject>Example DAE File</subject>
   </asset>
   <library_geometries>
      (Data defining the graphic's shape)
   </library_geometries>
   <library_controllers>
      (Data defining the graphic's structure)
   </library_controllers>
   <library_materials>
      (Data defining the graphic's appearance)
   </library_materials>
</COLLADA>

The <asset> element contains top-level header information, but the real description of graphic content is contained in the library elements. The rest of this section describes three of them:

  • <library_geometries>: Defines the vertices in the object’s mesh

  • <library_controllers>: Defines how the mesh vertices relate to bones

  • <library_materials>: Defines parameters related to the object’s material rendering

These elements are called libraries because they can be incorporated into separate files that can be accessed with <asset> elements. This modular usage is similar to that of regular code libraries.

The <library_geometries> Element

The <library_geometries> element contains much of the same information as in an Ogre3D mesh file. It holds data related to a graphic’s vertices, triangles, and overall geometry. A <library_geometries> element has three possible subelements:

  • <asset>: Identifies the library metadata (see previous element)

  • <geometry>: Contains information about the graphic’s shape

  • <extra>: Additional information related to the graphic

The most important of these is the <geometry> element, which contains the graphic’s geometric definition. It has two optional attributes: name and id. The name attribute is a human-readable label and id provides a unique identifier for the element.

Only one of <spline>, <mesh>, or <convex-mesh> can be included in a given <geometry> element. Each defines a different shape:

  • <spline>: A curve defined by a polynomial. Includes linear splines, Bézier curves, B-splines, and Nonuniform rational B-splines (NURBS).

  • <mesh>: A structure composed of vertices organized into lines, triangles, and polygons.

  • <convex-mesh>: Similar to <mesh>, but may derive its structure from another <geometry> element.

This presentation focuses on the <mesh> element, which holds the same type of vertex information as in the Ogre3D ninja.mesh file. The most important subelement of <mesh> is <source>.

The <source> Subelement

A <mesh> usually has multiple <source> elements, each containing numeric values related to the same set of vertices. One <source> element might contain vertex positions. while another holds normal vector parameters. A third <source> element may store texture coordinates. These elements are distinguished by their id attribute, which is required and must be unique.

Information in <source> elements usually consists of two parts. The first part holds a numeric array within a subelement that identifies the datatype of the numbers. The second part specifies how the numbers should be accessed. This is contained within <technique_common> or <technique> subelements.

There are many possible subelements that hold numeric arrays. <float_array>, <int_array>, and <bool_array> are the most popular. Only one array can be included in a <source> element. In each case, the only required attribute is count, which represents the number of values inside the element.

For example, the following <source> element contains 12 floating-point values. The only required attribute of <source> is id, and the only required attribute of <float_array> is count:

<source name="Example Source" id="example_source">
   <float_array count="12" id="example_array">
      0.1 0.2 0.3 0.4 0.5 0.6
      0.7 0.8 0.9 1.0 1.1 1.2
   </float>
</source>

This example doesn’t say anything about the purpose of these numeric values: They could be coordinates or color values or polynomial coefficients. The <source> element just lists them in order and matches them to an identifier. The interpretation of these values is identified by the <technique> or <technique_common> subelement.

A technique specifies the manner in which a graphic should be displayed on a target platform. An object may need to be rendered one way on mobile platforms and another way on personal computers. For each method of presentation, you should have a separate <technique> subelement under the <source>. In many cases, however, you’ll provide only a single technique that applies to all platforms. This is identified with <technique_common>.

As a child of <source>, the <technique_common> element contains a single child of its own: <accessor>. This defines how the values in the array should be accessed. It does this by partitioning the source array into subarrays with specific names. For example, the following code defines the same 12-element array as before, but the accessor divides the values into four subarrays with three elements (x, y, and z) each:

<source name="Example Source" id="example_source">
   <float_array count="12" id="example_array">
      0.1 0.2 0.3 0.4 0.5 0.6
      0.7 0.8 0.9 1.0 1.1 1.2
   </float>
   <technique-common>
      <accessor count="4" source="#example_source" stride="3">
         <param name="x" type="float"/>
         <param name="y" type="float"/>
         <param name="z" type="float"/>
      </accessor>
   </technique-common>
</source>

The <technique_common> element has two required attributes: source and count. The first defines the source element whose numeric arrays will be accessed. Notice that the name of the source element is preceded with a #. This states that the text that follows is an id value. The count attribute identifies how many subarrays the source array will be divided into.

The stride attribute isn’t required, but it shows how many array elements should be accessed at a time. With each access, the array elements are assigned names identified in the param declarations. In the example, the array is accessed three elements at a time, and the floating-point values are named x, y, and z in succession.

The <vertices> Subelement

Many of the other <mesh> subelements identify how the <source> values should be used. The <vertices> subelement matches subarrays in the <source> element with coordinates in the mesh. It has one required attribute, id, and must have at least one child element, <input>.

The <input> element identifies the <source> of the numeric values and states that they represent positions within the mesh. The following <vertices> element accesses the values in the example_source element and identifies them as vertex positions:

<vertices id="example_vertices">
   <input semantic="POSITION" source="#example_source"/>
</vertices>

Each <input> inside a <vertices> element must have a semantic attribute equal to POSITION and a source attribute equal to a defined source element. It may also have an optional offset attribute that identifies a position inside the array. A <vertices> element may have multiple <input> elements identifying different <source> elements.

Before proceeding further, let’s summarize the elements discussed so far:

  • The <source> element defines an array of numeric values.

  • The <source> element contains a <technique_common> element that partitions the numeric values into groups.

  • The <vertices> element identifies the numeric groups in the <source> element as being coordinates of vertices in a mesh.

The vertices can now be combined to create shapes, such as lines, triangles, and polygons. This is discussed next.

COLLADA Shapes

Chapter 20, “OpenGL on the Cell: Gallium and Mesa,” explained how OpenGL creates shapes from vertices. COLLADA shapes are similar, and are defined by one of the following:

  • <lines>: Vertices are accessed in twos and form separate lines.

  • <linestrips>: Each new vertex forms a connected line.

  • <triangles>: Vertices form separate triangles.

  • <tristrips>: Each new vertex forms a triangle.

  • <trifans>: Each new vertex forms a triangle connected at the first point.

  • <polygons>: Vertices form multisided shapes that may contain holes.

  • <polylist>: Vertices form multi-sided shapes that cannot contain holes.

These shape elements all have the same three attributes: name, count, and material. Only count is required, and states how many shapes will be constructed. material is optional and identifies a defined material. If the mesh material isn’t defined, properties like light and shading must be defined elsewhere.

Each shape element has at least three subelements: <input>, <p>, and <extra>. The <input> subelement has the same attributes as the <input> subelement inside <vertices>. But the offset attribute is required and possible values for semantic include VERTEX, COLOR, NORMAL, TEXCOORD, TEXTURE, TANGENT, BINORMAL, and UV. Different values of semantic refer to different <source> elements, and a shape element may contain multiple <input> values to represent different vertex data.

The <p> subelement creates shapes from vertices. Each vertex is assigned an index, and the <p> element lists indices in the order in which they form the named shape. An example will clarify how <p> is used. Let’s say you want to create two triangles from five vertices: Triangle 1 consists of P0, P1, and P2, and Triangle 2 consists of P1, P3, and P4. The <triangles> element would be declared as follows:

<triangles count="2">
   <input offset="0" semantic="VERTEX" source="vertex_data">
   <p>0 1 2 1 3 4</p>
</triangles>

Instead of listing all the indices in a single <p> element, a separate <p> subelement can be declared for each shape. For this simple example, the triangles are created with the following:

<triangles count="2">
   <input offset="0" semantic="VERTEX" source="vertex_data">
   <p>0 1 2</p>
   <p>1 3 4</p>
</triangles>

To show how a shape element accesses multiple inputs, the next <triangles> element associates each vertex index with an index into an array of normal data. Here, the vertices are defined in the vertex_data source, and the normal data is defined in the normal_data source:

<triangles count="2">
   <input offset="0" semantic="VERTEX" source="vertex_data">
   <input offset="1" semantic="NORMAL" source="normal_data">
   <p>0 0  1 1  2 3</p>
   <p>1 4  3 5  4 6</p>
</triangles>

There are two inputs and twice as many values in the <p> elements. In this case, the <p> values are grouped in twos: The first value in each group is taken from the input with offset 0, and the second is taken from the input with offset 1. Each vertex index is matched with a corresponding normal vector.

The shape determines how many vertices are accessed at a time, but polygons may have an arbitrary number of points. For this reason, the vertex indices are separated into individual <p> elements rather than being grouped into a single list. For example, the following element defines two polygons, the first consisting of five vertices and the second consisting of seven:

<polygons count="2">
   <input offset="0" semantic="VERTEX" source="vertex_data" />
   <p>0 3 2 4 1</p>
   <p>3 7 5 8 6 9 10</p>
</polygons>

If the polygon contains holes, the <ph> element is used. This contains subelements <p> and <h>, where <p> represents the vertices of the polygon and <h> represents vertices of the hole.

The last shape element is <polylist>, which differs from <polygons> in two respects. First, it identifies how many vertices are in each polygon with the <vcount> subelement. This makes it possible to specify all the vertices in a single list. The following code shows how a <polylist> can declare the same two polygons as those declared previously:

<polylist count="2">
   <input offset="0" semantic="VERTEX" source="vertex_data" />
   <vcount>5 7</vcount>
   <p>0 3 2 4 1 3 7 5 8 6 9 10</p>
</polylist>

The second difference between <polylist> and <polygons> is that no holes are allowed in a <polylist> declaration.

The <library_controllers> Element

Ogre3D skeleton files define bones and joints in an object’s skeleton. In COLLADA, this information is contained in a <library_controllers> element. This element contains one or more <controller> elements.

The primary subelements of <controller> are <skin> and <morph>. The <skin> subelement contains much of the same information as in an Ogre3D skeleton file, such as joint identification and vertex weighting. The <morph> subelement is completely new, and contains information related to animating individual vertices.

The <skin> Subelement

Ogre3D skeleton files group vertices and polygons into bones, which move as individual objects during animation. A bone’s position is determined by the joint that connects it with its parent bone. The entire parent-child bone hierarchy forms the object’s skeleton. The vertices and polygons covering the skeleton form its skin.

The goal of the <skin> element is to show how vertices combine to form bones and how the bones move. It’s important to understand the coordinates and transformations involved. An object’s bind shape is formed of the vertices that make up its skin. The inverse bind matrix transforms a vertex’s world coordinates so that it can be expressed in the local coordinates used by its joint.

The bind shape matrix transforms the local coordinates so that the vertices move as their joints move. This is identified with the optional <bind_shape_matrix> element, which is set to the identity matrix by default. Both matrices have to be identified for animation to work properly.

Like the <mesh> element described earlier, the <skin> element places its numeric values inside <source> elements. At least three <source> elements are required for each <skin> element:

  • A <source> element that lists the names of the joints

  • A <source> element that identifies the weight of each vertex

  • A <source> element that defines inverse bind matrices

These values are accessed by the other <skin> subelements: <joints> and <vertex_weights>.

The <joints> element creates an association between the names of the joints and their attributes. At the very least, it has to match joint names to inverse bind matrices. This is shown with the following code:

<joints>
   <input semantic="JOINT" source="#example_joints" />
   <input semantic="INV_BIND_MATRIX" source="#example_ibms" />
</joints>

As the skeleton moves, multiple bones may influence a vertex’s transformation. This information is provided by the <vertex_weights> element. The numeric weights are identified in a <source> element and accessed through an <input> element. A second <input> element must identify the <source> element holding the names of the joints.

The <vertex_weights> attribute has a required count attribute that specifies the number of vertices. For each vertex, the <vcount> element contains the number of bones that influence it. Finally, the <v> parameter matches the bone index with the weight index. If the bone index is −1, the weight is matched to the entire body pose.

For example, the following declaration identifies weighting for three vertices:

<vertex_weights count="3">
   <input semantic="JOINT" source="#example_joints" />
   <input semantic="WEIGHT" source="#example_weights" />
   <vcount>3 2 4</vcount>
   <v>-1 0  1 1  4 2  -1 3  2 4  -1 5  2 6  3 7  4 8</v>
</vertex_weights>

The first vertex is affected by Bones −1, 1, and 4; the second is affected by Bones −1 and 2; and the third is affected by −1, 2, 3, and 4. Each influence is identified by sequential weights indexed from zero to eight.

The <morph> Subelement

Morphing is similar to animation, but transforms each vertex separately, without bones or a skeleton structure. This process interpolates values between a base mesh and a target mesh, and transforms each vertex of the base mesh until they form the target mesh. The nature of the transformation is affected by the base mesh, the target mesh, and vertex weighting.

There are two main methods of morphing vertices. The first, called normalized morphing, normalizes vertex weights so that their sum equals one. Then it performs the transformation using the following equation:

The <morph> Subelement

The relative method is simpler, and is given by the following formula:

The <morph> Subelement

The <morph> element sets up the transformation by matching target meshes and vertex weights. One attribute, method, identifies the morphing method, and source names the symbol corresponding to the base mesh. It also has three subelements: <source>, <targets>, and <extra>. The <targets> element contains at least two <input> elements. This is shown in the following declaration:

<morph method="normalized" source="#example_base">
   <input semantic="MORPH_TARGET" source="#example_target" />
   <input semantic="MORPH_WEIGHT" source="#example_weight" />
</morph>

In this example, the morphing process transforms the vertices in example_base mesh into the example_target mesh using the normalized method and the weights in the example_weight source element.

The <library_materials> Element

Ogre3D material scripts define visual properties like an object’s reflectance, texture, and shading. A COLLADA file provides the same information with the <library_materials> element. This consists of one or more <material> elements.

Each <material> element defines material properties with a single required <instance_effect>. The <instance_effect> has a url attribute that identifies the location of the object to be rendered. It also has two important subelements: <technique_hint> and <setparam>. The first identifies target platforms and profiles, and the second specifies material parameters. Both are optional.

The <technique_hint> Subelement

Before defining the rendering properties of a material, it’s important to specify what rendering platform should be used. The <technique_hint> subelement identifies a platform that the material properties are intended for. A <material> element may have more than one <technique_hint> to identify multiple suitable platforms.

The <technique_hint> element has three attributes and no subelements. The attributes are as follows:

  • platform: Name of the intended platform

  • ref: Unique identifier for the technique hint

  • profile: Name of the renderer/shader to be used

Only the ref attribute is required. Examples of profile values include profile_COMMON, profile_CG, profile_GLES, and profile_GLSL. In each case, the attribute’s value is set to the profile name without the profile_. This is shown in the following declarations:

<technique_hint platform="PS3" ref="texture" profile="CG"/>
<technique_hint ref="shader" profile="GLSL"/>

In this example, profile_CG should be used for texture on the PS3, and profile_GLSL should be used for shading.

The <setparam> Subelement

<setparam> assigns a value to a previously identified parameter. The name of the parameter is identified by the ref attribute, which is required for all <setparam> elements. Its value is specified by placing numbers or characters between tags that represent datatypes. Table 22.2 lists the datatypes available for parameter values.

Table 22.2. <setparam> Parameter Datatypes

Datatype

Description

bool

Boolean (true/false) value

booldim

Array of dim Boolean values, dim between 2 and 4

int

Integer value

intdim

Array of dim integer values, dim between 2 and 4

float

Floating-point value

floatdim

Array of dim floating-point values, dim between 2 and 4

floatdim1xdim2

Two-dimensional array of floating-point values, where dim1 and dim2 are between 2 and 4

surface

Source of texture samples

sampler1D

One-dimensional texture sampler

sampler2D

Two-dimensional texture sampler

sampler3D

Three-dimensional texture sampler

samplerCUBE

Texture sampler for cube maps

samplerRECT

Texture sampler for two-dimensional maps

samplerDEPTH

Texture sampler for depth maps

enum

Enumerated value

The parameter can be set to a single value or an array of values. For numbers, one-dimensional arrays are identified with a number following the datatype (for instance, int3), and two-dimensional arrays are identified with two numbers following the datatype, such as float3x2. Parameters can also be set to previously declared objects, such as surfaces and samplers.

As an example, the following <setparam> declaration assigns the array (0.25 0.50 0.75) to the parameter emit_color:

<setparam ref="emit_color">
   <float3>0.25 0.50 0.75</float>
</setparam>

The COLLADA standard provides for a number of other libraries and elements, and many of these are needed to fully flesh out an object’s properties and rendering. For a thorough treatment of the subject, I strongly recommend COLLADA: Sailing the Gulf of 3-D Content Creation, by Remi Arnaud and Mark Barnes.

The COLLADA Application Programming Interface (API)

Many conversion utilities are available to transform digitally created content into the COLLADA format. However, if you’re designing games, you’ll need to access this content in code. This section explains how to obtain the COLLADA libraries and use them to build applications.

Installing the COLLADA Libraries

Before you can use the COLLADA libraries, you need to install two dependencies: Perl Compatible Regular Expressions (PCRE) and the XML library. To install these modules, enter the following at the command line:

yum install pcre pcre-devel libxml2 libxml2-devel

To access DAE elements in code, the COLLADA libraries must be compiled from source. The source code for the COLLADA project is hosted at SourceForge: http://sourceforge.net/projects/COLLADA-dom. Click the Download link and save the archive to your system.

Change to $COLLADA/dom and enter make. This creates a folder called build and a directory structure beneath it. At the time of this writing, the library libcollada14dom can be found in $COLLADA/dom/linux-1.4. This library provides the routines that make it possible to access COLLADA elements in code.

There are at least two ways to make sure this library is included into the build. You can add a line to /etc/ld.so.conf identifying the directory containing libcollada14dom. Then run ldconfig to update the dynamic loader cache.

Alternatively, you can update the LD_LIBRARY_PATH environment variable. Either enter the following command at the command line

export LD_LIBRARY_PATH=$COLLADA/dom/linux-1.4:$LD_LIBRARY_PATH

or permanently change the LD_LIBRARY_PATH environment variable in .bashrc.

Basic Objects of the COLLADA API

Let’s start with a simple example. The code in Listing 22.1 performs three operations. It reads a DAE file’s contents into the runtime database, displays the number of documents in the database, and then frees memory. The application is trivial, but it provides insight into how the basic DAE classes operate.

Example 22.1. Introduction to the COLLADA API: dae_basic.cpp

#include <iostream>
#include <dae.h>

using namespace std;

int main(int, char **) {
   unsigned int docnum;

   /* Create a DAE object */
   DAE* dae = new DAE;

   /* Load file content and access the database */
   dae->load("example.dae");
   dae->setDatabase(0);

   /* Insert document into database */
   daeDocument *daeDoc = NULL;
   dae->getDatabase()->insertDocument("example.dae",
      &daeDoc);

   /* Obtain and display number of documents */
   docnum = dae->getDatabase()->getDocumentCount();
   cout << "Document count: " << docnum << endl;

   /* Unload document and deallocate DAE */
   dae->unload("example.dae");
   delete dae;
   return 0;
}

This application relies on three objects:

  • DAE: Loads and saves documents, accesses the database

  • daeDatabase: Provides access to documents and elements

  • daeDocument: Represents a document in the database

This presentation discusses each of these classes and then explains how to analyze the document with the COLLADA Document Object Model (DOM).

The DAE Class

As shown in Listing 22.1, the primary object in the COLLADA API is the DAE. This opens the COLLADA file and creates a document object with the same name. The XML elements in the document are cached inside a runtime database, which may contain elements of one or more documents. The elements can then be read, written to, and saved to a file. Table 22.3 lists some of the DAE functions that make this possible.

Table 22.3. Functions of the DAE Class

Datatype

Description

load(daeString URI)

Creates a document from the specified URI

unload(daeString docName)

Unloads the named document

clear()

Unloads all documents in the database

save(daeString docName)

Stores the named document to its original URI

save(daeUInt docIndex)

Stores the indexed document to its original URI

saveAs(daeString URI, daeString docName)

Stores the named document to the named URI

saveAs(daeString URI, daeString docName)

Stores the indexed document to the indexed URI

getDatabase()

Returns a pointer to the daeDatabase object being used to store elements

setDatabase(daeDatabase* db)

Specifies the daeDatabase object to be used

The first seven functions in the table are concerned with documents stored in the runtime database. They can be accessed by name or index, and can be loaded, unloaded, or saved as needed. The save function stores the document to the URI it was extracted from, and saveAs saves it to a new URI.

getDatabase returns a pointer to the runtime database. This is encapsulated within a daeDatabase object whose functions access the list of documents and their elements. The default database can be accessed by calling setDatabase with 0 or NULL as the argument:

dae->setDatabase(0);

The database can now be used to create documents and analyze their structure.

The Runtime Database and the DAE Elements

The daeDatabase class provides access to documents stored in the database. These documents can be inserted, as shown in the code in Listing 22.1, or directly created inside the database. In addition, the database also provides access to the elements inside the documents. Table 22.4 presents the functions that perform these tasks.

Table 22.4. Functions of the daeDatabase Class

Datatype

Description

createDocument(daeString docName, daeDocument* doc)

Creates the named document in the database and returns a pointer (doc can be null)

createDocument(daeString docName, daeElement* dom, daeDocument* doc)

Creates the named document into the database, sets the root of the document to dom, and returns a pointer (doc can be null)

insertDocument(daeDocument* doc)

Inserts an existing document into the database

insertDocument(daeString docName, daeDocument* doc)

Inserts the named document into the database and returns a pointer (doc can be null)

insertDocument(daeString docName, daeElement* dom, daeDocument* doc)

Inserts the named document into the database, sets the root of the document to dom, and returns a pointer (doc can be null)

removeDocument(daeDocument* doc)

Removes the document from the runtime database

clear()

Removes all documents from the runtime database

getDocument(daeString docName)

Returns a pointer to the named document

getDocument(daeUInt docIndex)

Returns a pointer to the indexed document

getDocument(daeUInt docIndex)

Returns the name of the indexed document

getDocumentCount()

Returns the number of inserted documents

isDocumentLoaded(daeString docName)

Returns whether the named document is in the database

insertElement(daeDocument* doc, daeElement* element)

Inserts an element in the document into the runtime database

removeElement(daeDocument* doc, daeElement* element)

Removes an element in the document out of the runtime database

getElementCount(daeString elementName, daeString elementType, daeString doc)

Returns the number of elements given the input parameters (which can all be null)

getElement(daeElement** elements, daeString elementName, daeString elementType, daeString doc)

Sets the input pointer to the daeElement identified by the input parameters (which can all be null)

 

Most of these functions deal with daeDocument objects: creating, inserting, accessing, and removing. A document corresponds to a DAE file, but may be created in other ways. A database’s documents can be accessed by name or by index.

Just as a DAE file is composed of XML elements, a daeDocument is composed of daeElements. These daeElements correspond directly to the file’s XML elements and are structured within the same hierarchy. This hierarchy is explored further in the next section, but it’s important to know that each document has a single root element. This is the daeElement parameter referenced by the createDocument and insertDocument functions.

The getElementCount and getElement functions require explanation. The first function returns the number of elements that meet the given criteria. The input parameters are given as follows:

  • elementName: The name or id attribute of the XML element

  • elementType: The type of the XML element, such as mesh or input

  • doc: The name of the document or file, such as example.dae

Each of these parameters can be set to NULL. This makes the search less restrictive.

The getElement function has the same search parameters as getElementCount, but sets the first parameter to point to the indexed element that meets the criteria. These two functions commonly work together: getElementCount determines how many elements meet the search parameters, and getElement searches for an element in a loop until the right element is found. This is shown in the following code, which searches for all animation elements in the database:

/* Find the number of elements */
unsigned int numElements = dae->getDatabase()->getElementCount(NULL,
"animation", NULL);

unsigned int i;
for(i=0; i<numElements; i++) {

   /* Access each element object */
   dae->getDatabase()->getElement((daeElement**)&element, i, NULL,
      "animation", NULL);

   /* Process element */
}

Instead of using the actual element name (animation in the preceding code), it’s better to access one of the constants in domConstants.h. For example, animation in the code above can be replaced by the COLLADA_ELEMENT_ANIMATION constant, which has the same value.

The COLLADA Document Object Model (DOM)

The daeDocument class doesn’t provide as many functions as DAE or daeDatabase, but its getDomRoot function is central to this discussion. This returns a domCOLLADA object that represents the top of the element hierarchy. This object can then be used to read and modify the information stored in the document.

Table 22.5 lists many of the domCOLLADA functions that make this possible. Function names preceded by (1) belong to the daeElement class, the superclass of domCOLLADA.

Table 22.5. Functions of the domCOLLADA/daeElement Classes

Datatype

Description

setXmlns(xsAnyURI nSpace)

Sets the namespace attribute (xmlns) in the document

getXmlns()

Returns the xsAnyURI object representing the document’s namespace attribute

getAsset()

Returns the domAssetRef object corresponding to the document’s <asset> declaration

getLibrary_XXX()

Returns a DOM object corresponding to the <library_XXX> declaration

getScene()

Returns a domSceneRef object corresponding to the document’s <scene> declaration

getExtra_array()

Returns an dom_ExtraArray object representing the array of <extra> elements

(1)createElement(daeString elementName)

Creates the element with the specified name

(1)placeElement(daeElement* element)

Adds the specified element as a child

(1)createAndPlace(daeString elementName)

Creates the element and adds it as a child

(1)placeElementAt(daeInt index, daeElement* element)

Adds the specified element as a child at the specified index

(1)createAndPlaceAt(daeInt index, daeString elementName)

Creates the element and adds it as a child at the specified index

(1)placeElementBefore(daeElement mark, daeElement* element)

Adds the specified element as a child before the mark element

(1)placeElementAfter(daeElement mark, daeElement* element)

Adds the specified element as a child after the mark element

(1)removeChildElement(daeElement element)

Removes the specified element as a child

(1)hasAttribute(daeString attrName)

Returns whether the element has the specified attribute

(1)getTypeName()

Returns a daeString containing the element’s type

(1)getAttributeValue(daeString attrName)

Returns a pointer to the attribute value

(1)setAttribute(daeString attrName, daeString attrVal)

Sets the attribute attrName equal to attrValue

(1)setElementName(daeString name)

Specifies the element’s name attribute

(1)getElementName()

Returns a daeString containing the element’s name

(1)getID()

Returns a daeString containing the element’s ID if it exists

The domCOLLADA functions provide access to the top-level elements of a DAE file: <asset>, <library_XXX>, <scene>, and <extras> elements. getLibrary_XXX refers to any of the possible library elements, including <library_geometries>, <library_controllers>, or <library_materials>. In each case, the object returned is specific to the element. For example, getAsset returns a domAssetRef object, and getScene returns a domSceneRef.

The functions of the daeElement class (superclass of domCOLLADA), provide many more capabilities for dealing with a document’s elements. Most of them are variations of createElement or placeElement, and it’s important to understand the distinction. createElement constructs a new daeElement object, but placeElement makes it a child of the element. In many cases, you’ll use a function that combines both tasks, such as createAndPlaceElement.

The last functions in the table deal with an element’s attributes. General attributes can be accessed with getAttributeValue or setAttribute. Specific attributes, such as an element’s name or ID, can also be accessed with getElementName, setElementName, and getID.

The daeElement class has no functions that read/write an element’s value. For example, you can set the name attribute of a float_array element with setElementName, but you can’t use any of these functions to change the actual floating-point values. Only specific types of elements have values, and only specific subclasses of daeElement have the setValue function available. This is shown in Listing 22.2, which creates a new DAE file whose <created> and <modified> element values are defined in code.

Example 22.2. Creating a DAE Document in Code: dom_create.cpp

#include <iostream>
#include <dae.h>
#include <dom/domCOLLADA.h>
#include <dom/domAsset.h>

using namespace std;

int main(int, char **) {

   /* Create document and insert into database */
   DAE* dae = new DAE;
   dae->setDatabase(0);
   daeDocument* daeDoc = NULL;
   dae->getDatabase()->insertDocument("new.dae", &daeDoc);

   /* Get root element of document and create asset */
   domCOLLADA* dom = (domCOLLADA*)daeDoc->getDomRoot();
   domAsset* asset =
      (domAsset*)dom->createAndPlace("asset");

   /* Set values for the created and modified elements */
   domAsset::domCreated* created =
      (domAsset::domCreated*)asset->
         createAndPlace("created");
   created->setValue("2008-03-25T14:21:29U");
   domAsset::domModified* modified =
      (domAsset::domModified*)asset->
         createAndPlace("modified");
   modified->setValue("2008-03-25T21:51:29U");

   /* Unload document and deallocate DAE */
   dae->save("new.dae");
   delete dae;
   return 0;
}

This application does not access an existing file. Instead, it constructs a daeDocument object, inserts it into the database, and adds content to the document. It adds content by accessing the DOM root and creating a domAsset. This represents the <asset> declaration in a DAE file. Then it calls createAndPlace to add two child elements: a domCreated and a domModified. Both of these classes have a setValue function, and the application calls this to set the document’s creation and modification date.

After the document’s elements and subelements have been created, the code saves the document to a file called new.dae. The content of new.dae is shown here:

<?xml version="1.0"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
    <asset>
        <created>2008-03-25T14:21:29U</created>
        <modified>2008-03-25T21:51:29U</modified>
    </asset>
</COLLADA>

As you can see, the application automatically generated attributes for the <?xml> and <COLLADA> elements. These do not have to be specifically identified in code.

Conclusion

The COLLADA methodology makes it possible to exchange digital content between modelers, renderers, and other graphical tools. The structure of a COLLADA document is involved, with many library elements, attribute formats, and custom datatypes. But a single digital asset exchange (DAE) file can be used across a broad field of applications.

The first part of this chapter presents the XML-based structure of DAE files. Each file must start with an <asset> element that identifies metadata such as the file’s author, authoring tool, and time of the file’s creation and modification. Afterward, DAE files provide graphic information in a series of library elements. This chapter has discussed the <library_geometries>, <library_controllers>, and <library_materials> elements, but the COLLADA standard defines many more.

In many cases, it’s easier to analyze DAE files as C++ objects than as text files. This is made possible through the COLLADA API, which is discussed in the second part of this chapter. The classes are simple to understand: The DAE is the primary object for accessing files and the daeDatabase stores documents. Using the COLLADA DOM, you can read and modify the information in these documents.

This chapter has covered only the rudiments of COLLADA. There are many more library elements and elements for configuring scenes in DAE files. Further, the COLLADA API provides many more classes and functions than those described here. For more information about this subject, you can find excellent documentation in the $COLLADA/doc directory.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.110.58