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:
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|
|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.|
|Node type:||Material list|
|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.|
|Description:||Stores additional information about the model that is usually used by custom applications (e.g. undo data used by an 3D editing application).|
|Node type:||Model keyframe|
|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.|
|Node type:||Polygon list|
|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.|
|Node type:||Vector list|
|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.|
|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.|
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 model4. Download and extract the following archive that contains test RMA files to your project folder:
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.