Skip to content

Exporter Technical Details

bartteunis edited this page Jan 10, 2023 · 12 revisions

This page is sort of a working document in which various "technical" things are described. It attempts to describe the inner workings of the SMF exporter in great detail. Since parts of the SMF export code are written very concisely it may be possible that what the code actually does isn't really clear.

The selection to be exported

There are quite a few ways in ways the data in a Blender scene can be mapped to SMF building blocks (models, textures, rigs, animations). The current release of the exporter (v0.8 and the newer code in dev) handles things in a fairly basic way: the scene selection (or all scene objects) is checked, mesh-like objects and the first armature object in the selection are considered for export. The only "animated" data that is exported is that of the armature, if one is present and has animation data.
Other objects in the Blender scene can have animation data as well however. The objects have a position, rotation and scale which could be represented by two nodes in the SMF rig: one for the (imaginary) bone's head and one for its tail. The bone head's node will always be detached so it can move around freely (i.e. have a translation relative to its parent node) More generally, it may be possible to represent the entire hierarchy of the scene being exported as a hierarchy of nodes in SMF.
This would have the added advantage of also including basic animations of separate models that aren't skinned to an armature. More importantly it would allow for very complex animations and even entire cutscenes to be exported to SMF.

Constructing a node list for SMF

This is done in the function smf_node_list. In most cases it are the tails of the bones of an Armature in Blender that correspond to nodes in the SMF skeleton. Blender's disconnected bones correspond to SMF's detached nodes and require a bit of extra attention.

Constructing SMF's bindmap in Blender

The bindmap contains those nodes that represent a bone in SMF. Only information of bones (or, alternatively, of nodes that represent bones) are sent to the shader.

Important Every SMF node that has a parent and is attached to it, represents a bone.

According to the above it is clear that not all bones in a Blender armature end up in SMF's bindmap.

Note The bindmap is constructed from the list of bones in smf_bindmap.

Remapping the skin indices and weights

Blender uses a lot of indirection to store just about everything. This is also true when it comes to the skinning info.
This part tries to give a short overview on how the exporter handles this.

Note This remapping happens in the function smf_skin_indices_weights.

To begin with, animated skinned 'MESH' objects are parented to an 'ARMATURE' object and have an 'Armature' modifier that causes the mesh to be deformed.
For every mesh object, we first find out which indices in mesh_object.vertex_groups are related to the armature by comparing the names of the vertex groups to the names of the bones of the armature. We store the indices here, as the actual vertices refer to a group index in the object's list of vertex groups.
Finally we get a direct link between vertex group index and SMF bone index by using the bindmap.

The following image tries to show the links in a more visual way:

The rig export in detail

This part is fairly complex and can likely be optimized and/or cleaned up.

In this step we loop through the list of nodes that is returned by smf_node_list. The way it is currently implemented it is not a full list of valid nodes. As explained above in the construction of the node list "most" Blender bones can be mapped quite easily to SMF nodes but it gets harder when disconnected bones are included. The SMF nodes that represent the disconnected Blender bones' heads are represented by a None value in the bones list. This indicates that the node at that position is a node representing a disconnected bone's head. So the bone to get the information from is located at the next position in the list.

The if bone: check that is done for every element in the bone list returns True on two conditions:

  • bone is equal to None
  • bone is equal to False

TODO Ideally this part gets rewritten since the current way of doing things is far from clean and readable.

Normals

Blender meshes store normal information of polygon/face normals in Mesh.polygon_normals and of vertices in Mesh.vertex_normals. These are the pre-calculated ones.

Together with a mesh's auto-smooth property and a polygon's use_smooth property, and other things such as sharp edges the final normal is determined.

In practice Custom Split Normals Data may also be used, for those situations where e.g. normals are copy-pasted or are pointed in a certain direction.

The final result of the vertex normals per face is always stored in the loop normals (MeshLoop.normal). By default all loop normals are (0, 0, 0) however. To calculate them Mesh.calc_normals_split can be used. After this, the updated values that take into account everything that Blender supports can be found in MeshLoop.normal.

IMPORTANT Loop normals are lost when using the BMesh Module. This is a known issue with Blender (T45151). Most operations have an equivalent in Mesh though.

Animation export

TODO

Older information

Important things:

  • nodes vs bones
  • format differences (v6, v7, ...)

Use a single root bone per bone hierarchy

SMF supports multiple bones leaving from the root node. In Blender you can create multiple bones at the same position but they are treated as separate root bones of different hierarchies. They don't share a parent node, since Blender armatures don't use nodes but rather bones.

Because of that, it is recommended that you always have a single root bone at the top of a bone hierarchy.

Importing compatible rigs from MakeHuman

The default settings of the Collada importer result in an armature that only contains disconnected bones. Tick Find Bone Chains to get a connected bone hierarchy.

Also, the FBX exporter may give better results (TODO: verify this).