diff --git a/FNA.Core.csproj b/FNA.Core.csproj
index 4f7e1e0f..4c3f707c 100644
--- a/FNA.Core.csproj
+++ b/FNA.Core.csproj
@@ -188,6 +188,7 @@
+
diff --git a/FNA.NetFramework.csproj b/FNA.NetFramework.csproj
index 9c6e7e2a..4aae2182 100644
--- a/FNA.NetFramework.csproj
+++ b/FNA.NetFramework.csproj
@@ -189,6 +189,7 @@
+
diff --git a/FNA.NetStandard.csproj b/FNA.NetStandard.csproj
index 64157a3d..0c22453f 100644
--- a/FNA.NetStandard.csproj
+++ b/FNA.NetStandard.csproj
@@ -188,6 +188,7 @@
+
diff --git a/FNA.csproj b/FNA.csproj
index 0a86e6da..d39a2096 100644
--- a/FNA.csproj
+++ b/FNA.csproj
@@ -259,6 +259,7 @@
+
diff --git a/src/Graphics/ModelBuilderEXT.cs b/src/Graphics/ModelBuilderEXT.cs
new file mode 100644
index 00000000..a7f77965
--- /dev/null
+++ b/src/Graphics/ModelBuilderEXT.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+ ///
+ /// Model mesh descriptor
+ ///
+ public class ModelMeshDescEXT
+ {
+ ///
+ /// Name of the model mesh
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Bounding Sphere of the model mesh
+ ///
+ public BoundingSphere BoundingSphere { get; set; }
+
+ ///
+ /// Parts of the model mesh
+ ///
+ public readonly List Parts = new List();
+
+ internal int Index { get; set; }
+
+ ///
+ /// Adds a mesh part to the model mesh
+ ///
+ /// The index buffer for this mesh part
+ /// The location in the index array at which to start reading vertices
+ /// The vertex buffer for this mesh part
+ /// The offset (in vertices) from the top of vertex buffer
+ /// The number of vertices used during a draw call
+ /// The number of primitives to render
+ ///
+ ///
+ public void AddModelMeshPart(IndexBuffer indexBuffer, int startIndex,
+ VertexBuffer vertexBuffer, int vertexOffset, int numVertices,
+ int primitiveCount)
+ {
+ if (indexBuffer == null)
+ {
+ throw new ArgumentNullException(nameof(indexBuffer));
+ }
+
+ if (startIndex < 0 || startIndex >= indexBuffer.IndexCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+ }
+
+ if (vertexBuffer == null)
+ {
+ throw new ArgumentNullException(nameof(vertexBuffer));
+ }
+
+ if (vertexOffset < 0 || vertexOffset >= vertexBuffer.VertexCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(vertexOffset));
+ }
+
+ if (numVertices <= 0 || numVertices > vertexBuffer.VertexCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(numVertices));
+ }
+
+ if (primitiveCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(primitiveCount));
+ }
+
+ var part = new ModelMeshPart()
+ {
+ IndexBuffer = indexBuffer,
+ StartIndex = startIndex,
+ VertexBuffer = vertexBuffer,
+ VertexOffset = vertexOffset,
+ NumVertices = numVertices,
+ PrimitiveCount = primitiveCount
+ };
+
+ Parts.Add(part);
+ }
+
+ ///
+ /// Creates the model mesh
+ ///
+ /// Graphics Device
+ ///
+ public ModelMesh Create(GraphicsDevice device)
+ {
+ var modelMesh = new ModelMesh(device, Parts)
+ {
+ Name = Name,
+ BoundingSphere = BoundingSphere,
+ };
+
+ return modelMesh;
+ }
+ }
+
+ ///
+ /// Model bone descriptor
+ ///
+ public class ModelBoneDescEXT
+ {
+ ///
+ /// Name of the model bone
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Transform of the model bone
+ ///
+ public Matrix Transform { get; set; }
+
+ ///
+ /// Meshes of the model bone
+ ///
+ public readonly List Meshes = new List();
+
+ ///
+ /// Children of the model bone
+ ///
+ public readonly List Children = new List();
+
+ internal int Index { get; set; }
+ }
+
+ ///
+ /// Grants ability to create a Model at the run-time
+ ///
+ public static class ModelBuilderEXT
+ {
+ ///
+ /// Creates the model
+ ///
+ /// Graphics Device
+ /// Meshes of the model
+ /// Bones of the model
+ /// Root bone of the model
+ ///
+ ///
+ public static Model Create(GraphicsDevice device, List meshes, List bones, ModelBoneDescEXT root)
+ {
+ if (meshes == null)
+ {
+ throw new ArgumentNullException(nameof(meshes));
+ }
+
+ if (bones == null)
+ {
+ throw new ArgumentNullException(nameof(bones));
+ }
+
+ if (root == null)
+ {
+ throw new ArgumentNullException(nameof(root));
+ }
+
+ // Assign indexes
+ for (var i = 0; i < meshes.Count; ++i)
+ {
+ meshes[i].Index = i;
+ }
+
+ for (var i = 0; i < bones.Count; ++i)
+ {
+ bones[i].Index = i;
+ }
+
+ // Create meshes
+ var modelMeshes = new List();
+ foreach (var desc in meshes)
+ {
+ var modelMesh = desc.Create(device);
+ modelMeshes.Add(modelMesh);
+ }
+
+ // Create bones
+ var modelBones = new List();
+ for (var i = 0; i < bones.Count; ++i)
+ {
+ var desc = bones[i];
+ var bone = new ModelBone
+ {
+ Index = i,
+ Name = desc.Name,
+ Transform = desc.Transform
+ };
+
+ foreach (var mesh in desc.Meshes)
+ {
+ var modelMesh = modelMeshes[mesh.Index];
+ modelMesh.ParentBone = bone;
+ bone.AddMesh(modelMesh);
+ }
+
+ modelBones.Add(bone);
+ }
+
+ // Assign children
+ for (var i = 0; i < bones.Count; ++i)
+ {
+ var desc = bones[i];
+ var bone = modelBones[i];
+
+ foreach (var child in desc.Children)
+ {
+ var childBone = modelBones[child.Index];
+ childBone.Parent = bone;
+ bone.AddChild(childBone);
+ }
+ }
+
+ // Create the model
+ var model = new Model(device, modelBones, modelMeshes)
+ {
+ Root = modelBones[root.Index]
+ };
+
+ return model;
+ }
+ }
+}