Custom Model Importer Sample
This sample shows you how to implement a custom model importer for the XNA Content Pipeline.Sample Overview
This sample shows you how to implement a custom model importer for the XNA Content Pipeline. The sample importer is for the popular 3D model format OBJ. It is capable of importing many simple .obj files and their accompanying material (.mtl) files. However, the sample does not fully support all possible OBJ models.
Any project that references the importer can use the importer. The importer can be selected automatically by file extension or manually through the Properties pane. See Using a Custom Importer or Content Processor for more information.
How the Sample Works
Before you dive into the code, you need to understand the .obj and .mtl file formats. The formats are well publicized online and somewhat human readable. This sample includes several test files in the Content directory. You may want to view the test files in a text editor before you continue.
Import and ImportMaterials perform file level operations for importing the .obj and .mtl files. Since these files share a common structure, both of their import methods invoke GetLineTokens to iterate over the lines in the file and parse the lines into whitespace-delineated tokens. Each line acts like a method call; the model and materials are built imperatively. ParseObjLine and ParseMtlLine perform the file type-specific parsing. The file-specific line parsing methods use the first token on a line to select which operation to perform.
Input handling is fairly involved in this sample, since each element of the scene must be updated when the camera position changes. For TileGrid especially, you should update the camera properties only as necessary. Thus, you will minimize the number of calls to DetermineVisibility. With that in mind, the property IsChanged in the Camera class indicates whether the camera changed during the frame. This is how you know whether you need to update the visibility.
GetLineTokens also performs one more interesting action. It sets the FragmentIdentifier for a given ContentIdentity to be the current line number. In the event the system throws an InvalidContentException error, ContentIdentity provides the file name and line number at which the error occurred. The FragmentIdentifier property is a string. Thus, it is possible to identify fragments by other means. For a given model, for example, an importer could easily report the name of the mesh that failed to be imported.
Importing Models
The easiest way to import model geometry is to use the MeshBuilder class. The XNA Framework documentation explains the MeshBuilder class in detail. For XNA models, vertices and indexes are stored at the ModelMesh level, but .obj files have a single shared pool of vertices at the Model level. In this sample, a new MeshBuilder is created for each group of faces that reference the shared pool. The entire vertex pool is added to each MeshBuilder. This occurs regardless if every vertex is referenced. Fortunately, the default ModelProcessor eliminates any unreferenced vertices when it optimizes the meshes.
The model importer constructs a root NodeContent at build time. NodeContent becomes a Model object at run time. Also, each MeshBuilder produces a MeshContent. MeshBuilder becomes a ModelMesh and one or more ModelMeshParts at run time.
| Note |
|---|
| This sample importer does not support scene or bone hierarchies. You can implement hierarchies by constructing a tree of NodeContent, BoneContent, and MeshContent objects. |
Importing Materials
If you encounter an "mtllib" instruction in ParseObjLine, the system calls ContentImporterContext.AddDependency and ImportMaterials. AddDependency provides the Content Pipeline with metadata about the .obj file. This ensures that its accompanying .mtl files will be rebuilt if they change. The behavior of ImportMaterials is similar to Import. Instead of MeshBuilder objects, ParseMtlLine fills BasicMaterialContent objects, which, at run time, become BasicEffect instances.