Hi all,
So I looked at code and old documentation, and I got to the conclusion that to load fbx directly without building xnb, I need to do the following steps:
- Use the FbxImporter to import the model as
NodeContent. - Use the ModelProcessor to convert into
ModelContent. - Emulate the work done in
ModelReader.Read(), but instead of working withContentReaderwork with theModelContentI have.
Now I managed to do the above and successfully load an fbx from file, but I feel like its wrong because I’m duplicating code from ContentReader and have to access internal method via reflection to make it work (BuildHierarchy). My code is not that long, but I was wondering if maybe there’s a more “proper” way to do this that I somehow missed and maybe I did all that work for nothing 
Here’s my code:
public Model LoadModel(string modelPath)
{
// get from cache
if (_loadedModels.TryGetValue(modelPath, out Model cached))
{
return cached;
}
// load fbx and get model content
var node = _fbxImporter.Import(modelPath, _importContext);
// convert to model content
ModelContent modelContent = _modelProcessor.Process(node, _processContext);
// extract bones
var bones = new List<ModelBone>();
foreach (var boneContent in modelContent.Bones)
{
var bone = new ModelBone
{
Transform = boneContent.Transform,
Index = bones.Count,
Name = boneContent.Name,
ModelTransform = modelContent.Root.Transform
};
bones.Add(bone);
}
// resolve bones hirarchy
for (var index = 0; index < bones.Count; ++index)
{
var bone = bones[index];
var content = modelContent.Bones[index];
if (content.Parent != null && content.Parent.Index != -1)
{
bone.Parent = bones[content.Parent.Index];
bone.Parent.AddChild(bone);
}
}
// extract meshes
var meshes = new List<ModelMesh>();
foreach (var meshContent in modelContent.Meshes)
{
// get params
var name = meshContent.Name;
var parentBoneIndex = meshContent.ParentBone.Index;
var boundingSphere = meshContent.BoundingSphere;
var meshTag = meshContent.Tag;
// extract parts
var parts = new List<ModelMeshPart>();
foreach (var partContent in meshContent.MeshParts)
{
// build index buffer
IndexBuffer indexBuffer = new IndexBuffer(_graphics, IndexElementSize.ThirtyTwoBits, partContent.IndexBuffer.Count, BufferUsage.WriteOnly);
{
Int32[] data = new Int32[partContent.IndexBuffer.Count];
partContent.IndexBuffer.CopyTo(data, 0);
indexBuffer.SetData(data);
}
// build vertex buffer
var vbDeclareContent = partContent.VertexBuffer.VertexDeclaration;
List<VertexElement> elements = new List<VertexElement>();
foreach (var declareContentElem in vbDeclareContent.VertexElements)
{
elements.Add(new VertexElement(declareContentElem.Offset, declareContentElem.VertexElementFormat, declareContentElem.VertexElementUsage, declareContentElem.UsageIndex));
}
var vbDeclare = new VertexDeclaration(elements.ToArray());
VertexBuffer vertexBuffer = new VertexBuffer(_graphics, vbDeclare, partContent.NumVertices, BufferUsage.WriteOnly);
{
vertexBuffer.SetData(partContent.VertexBuffer.VertexData);
}
// create and add part
#pragma warning disable CS0618 // Type or member is obsolete
ModelMeshPart part = new ModelMeshPart()
{
VertexOffset = partContent.VertexOffset,
NumVertices = partContent.NumVertices,
PrimitiveCount = partContent.PrimitiveCount,
StartIndex = partContent.StartIndex,
Tag = partContent.Tag,
IndexBuffer = indexBuffer,
VertexBuffer = vertexBuffer
};
#pragma warning restore CS0618 // Type or member is obsolete
parts.Add(part);
}
// create and add mesh to meshes list
var mesh = new ModelMesh(_graphics, parts)
{
Name = name,
BoundingSphere = boundingSphere,
Tag = meshTag,
};
meshes.Add(mesh);
// set parts effect (note: this must come *after* we add parts to the mesh otherwise we get exception).
foreach (var part in parts)
{
var effect = EffectsGenerator != null ? EffectsGenerator(modelPath, modelContent, part) ?? DefaultEffect : DefaultEffect;
part.Effect = effect;
}
// add to parent bone
if (parentBoneIndex != -1)
{
mesh.ParentBone = bones[parentBoneIndex];
mesh.ParentBone.AddMesh(mesh);
}
}
// create model
var model = new Model(_graphics, bones, meshes);
model.Root = bones[modelContent.Root.Index];
model.Tag = modelContent.Tag;
// we need to call BuildHierarchy() but its internal, so we use reflection to access it ¯\_(ツ)_/¯
var methods = model.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
var BuildHierarchy = methods.Where(x => x.Name == "BuildHierarchy" && x.GetParameters().Length == 0).First();
BuildHierarchy.Invoke(model, null);
// add to cache and return
_loadedModels[modelPath] = model;
return model;
}
Am I doing it right or is there a more decent way?
PS to answer the question why I don’t use the MonoGame content tool and xnb - I want to make something that is more approachable for users to modify and add their own packages. while adding a conversion tool is possible and not that big of a deal, its still a barrier for many people IMO.
Thanks!