Home   Donators   Contact Us       

<< Previous Tutorial     Next Tutorial >>

Tutorial 20 - File IO

Theory

In this tutorial we will design a binary file format that can be used to save and load 3D models. With such a file format you will be able to work with more advanced 3D models and publish models with your applications (e.g. characters in games, trees in a landscape application, bones in a medical application, etc.).

Before we jump into designing the most awesome 3D file format ever, let's first define the goals of our file format:

  • It is easy to parse (load) information from the file.
  • Stores all the geometric information about the model (vertexes, polygons, etc.).
  • Stores material information about the model (colors, materials, textures, etc.).
  • Stores keyframe information about the model (e.g. different positions of a hand or a mechanical object).
  • All texture and height map images are embedded in the file (no dependencies on external files).
  • File format is downward and upward compatible (e.g. Version 1 of your 3D engine can parse files saved by version 20 and vice versa).
  • Faster loading is a higher priority than a small file size.
  • Format has ability to store custom meta data (e.g. custom fields used by your 3D editor program).
  • The file format belongs to the public domain. That means the file format can be used without any restrictions or requiring any permission, in your commercial and non-commercial applications.

As with any good file format, we will start by choosing a name for the file format. The file format will be branded as:

R3 Model Asset (*.rma)

The first structure we will define for our format is the binary file header. This header is used by programs to identify the file as a R3 Model Asset and to store general information about the file such as its version number.

Note that the /0 escape code represents the ASCII character 0. All numerical values are stored using the little-endian format.

The Signature field identifies the file as being a R3 Model Asset file.

The major and minor version fields is used to indicate the file format version that was used when the file was created. E.g. if MajorVersion equals 1 and MinorVersion equals 0, then the file format version is 1.0.

R3 Model Asset files are built using a collection of nodes. A node can represent any item or items that is required by the model, e.g. vertexes, polygons, texture maps or any other element that is needed by the model. The NodeCount field indicates how many nodes are stored in the file, whereas the FirstNodeOffset points to the location (offset) in the file where the first node is stored.

The structure of a node is given below:

The first NodeID field identifies the type of node. Commonly used IDs include "veclist/0", "polylist" and "texture/0".

We store the size of the node's data section in the Size field.

The NextNodeOffset field serves the same purpose as the FirstNodeOffset field in the header, and points to the location (offset) in the file where the next node is stored.

The binary data of the node is stored in the Data byte array field. Each node's data structure is different depending on its type. The diagram below illustrates the high-level view of a R3 Model Asset file format.

We will now take a look at the data section of each individual node type found in the R3 Model Asset file format.


Node type:   Color list
Node ID:   "collist/0"
Description:   Stores an array of RGBA colors. When this node is encountered, the parser simply appends all the colors in this list to the current SurfaceColor array of the model.
Data structure:  


Node type:   Material list
Node ID:   "matlist/0"
Description:   Stores an array of materials. When this node is encountered, the parser simply appends all the materials in this list to the current Material array of the model.
Data structure:  


Node type:   Metadata
Node ID:   "metadata"
Description:   Stores additional information about the model that is usually used by custom applications (e.g. undo data used by an 3D editing application).
Data structure:  


Node type:   Model keyframe
Node ID:   "modkeyfr"
Description:   Stores a keyframe of the model. When this node is encountered, the parser simply appends the keyframe to the array of keyframes in the model. The first node of type "Model keyframe" stores the default position, scale and rotation values of the model, but no joint structure.
Data structures:  
 


Node type:   Polygon list
Node ID:   "polylist"
Description:   Stores a list of the geometric data of polygons. When this node is encountered, the parser simply appends all the polygons in this list to the current Polygon array of the model.
Data structure:  


Node type:   Vector list
Node ID:   "veclist/0"
Description:   Stores an array of 3D vertexes. When this node is encountered, the parser simply appends all the vertexes in this list to the current vertex array of the model.
Data structure:  


Node type:   Texture
Node ID:   "texture/0"
Description:   Stores a RGBA bitmap and an optional height map that is used to texture map polygons. When this node is encountered, the parser simply appends the texture and optional height map to the current Texture array of the model. The first pixel in the data section is the top-left pixel of the bitmap. The second pixel is the second pixel in the data section is the pixel to the immediate right of the first pixel. The last pixel in the data section is the bottom-right pixel of the bitmap.
Data structure:  


Tutorial Steps
Tutorial created with Real Studio 2012 Release 1.
1. Download and extract the following project base to a new folder:
2. Open the project file with Real Studio.
3. Add the following code to the Action event of cmdOpen:
Dim dlg As new OpenDialog
Dim modFile As FolderItem
Dim model As R3Model
Dim rmaType As New FileType

' configure R3 Model Asset (*.rma) file type

rmaType.Name = "R3 Model Asset"
rmaType.MacType = "RMA"
rmaType.MacCreator = "rma"
rmaType.Extensions = "rma"

dlg.Filter = rmaType

modFile = dlg.ShowModal()

' did the user select a R3 Model Asset (*.rma) file to open?
if modFile <> nil then

' yes, so let's load the model into our scene, and refresh the OpenGL surface

' first remove all the current models from the scene
while Scene.Model.Ubound >= 0
  Scene.Model.Remove Scene.Model.Ubound
wend

' load the model from the file
model = R3_LoadModel(modFile)

' add the model to our scene
Scene.AppendModel model

OpenGLSurface1.Render ' refresh the OpenGL surface

end if

DisplayFrames ' display the keyframes available for the model
4. Download and extract the following archive that contains test RMA files to your project folder:
5. Save and run the project. Open the test R3 Model Asset (*.rma) files and rotate the models on the screen with your mouse cursor:
  • model1.rma - Simple geometric model
  • model2.rma - Model with color polygons
  • model3.rma - Model with material polygons
  • model4.rma - Model that is scaled, rotated and translated
  • model5.rma - Texture mapped model (without height map)
  • model6.rma - Texture mapped model (with height map)
  • model7.rma - Model with keyframes (Click on the keyframes)
Code Analysis

The first important change to take note of (if you worked through the previous tutorials), is that we moved the array that holds our textures from the R3Scene object to the R3Model object. This decouples the model completely from the scene object and makes it easier for us to load and save models, to and from files, without affecting the scene.

The functions to save and load models are in the R3_FileIO module. To use these functions are very straightforward.

Use R3_SaveModel to save a model in memory to a file. You simply pass a FolderItem object and the R3Model object that you want to save, as parameters to the function. The file will be created at the location specified by the FolderItem object.

To load a model is just as easy. Simply pass a FolderItem, that specifies the location of a RMA file, to R3_LoadModel and store the object returned by R3_LoadModel in a R3Model variable. On line 28 of the cmdOpen.Action event we use the R3_LoadModel function to load a model from a file into memory.

The R3_LoadModel and R3_SaveModel functions are really easy to use. See if you can save one of your own R3Model objects to a file, using R3_SaveModel.

Project Downloads

<< Previous Tutorial     Next Tutorial >>


 
All the content on Real 3D Tutorials, with the exception of the SyntaxHighlighter which is licensed under the MIT License, is provided to the public domain and everyone is free to use, modify, republish, sell or give away this work without prior consent from anybody. Content is provided without warranty of any kind. Under no circumstances shall the author(s) or contributor(s) be liable for damages resulting directly or indirectly from the use or non-use of the content.
Should you find the content useful and would like to make a contribution, you can show your support by making a donation.