diff --git a/MonoGame.Framework.Content.Pipeline/FbxImporter.cs b/MonoGame.Framework.Content.Pipeline/FbxImporter.cs
index 1f9a3823b85..bcbb6b792d7 100644
--- a/MonoGame.Framework.Content.Pipeline/FbxImporter.cs
+++ b/MonoGame.Framework.Content.Pipeline/FbxImporter.cs
@@ -3,6 +3,10 @@
// file 'LICENSE.txt', which is part of this source code package.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Assimp;
+using Assimp.Configs;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline
@@ -10,25 +14,214 @@ namespace Microsoft.Xna.Framework.Content.Pipeline
///
/// Provides methods for reading AutoDesk (.fbx) files for use in the Content Pipeline.
///
+ ///
+ /// Since OpenAssetImporter supports lots of formats, there's little that stands in the
+ /// way of adding more file extensions to the importer attribute and suporting more.
+ ///
[ContentImporter(".fbx", DisplayName = "Fbx Importer - MonoGame", DefaultProcessor = "ModelProcessor")]
public class FbxImporter : ContentImporter
+ {
+ public override NodeContent Import(string filename, ContentImporterContext context)
+ {
+ var identity = new ContentIdentity(filename, GetType().Name);
+ var importer = new AssimpImporter();
+ importer.AttachLogStream(new LogStream((msg, userData) => context.Logger.LogMessage(msg)));
+ var scene = importer.ImportFile(filename,
+ PostProcessSteps.FlipUVs | // So far appears necessary
+ PostProcessSteps.JoinIdenticalVertices |
+ PostProcessSteps.Triangulate |
+ PostProcessSteps.SortByPrimitiveType |
+ PostProcessSteps.FindInvalidData
+ );
+
+ var rootNode = new NodeContent
+ {
+ Name = scene.RootNode.Name,
+ Identity = identity,
+ Transform = ToXna(scene.RootNode.Transform)
+ };
+
+ // TODO: Materials
+ var materials = new List();
+ foreach (var sceneMaterial in scene.Materials)
+ {
+ var diffuse = sceneMaterial.GetTexture(TextureType.Diffuse, 0);
+
+ materials.Add(new BasicMaterialContent()
+ {
+ Name = sceneMaterial.Name,
+ Identity = identity,
+ Texture = new ExternalReference(diffuse.FilePath, identity)
+ });
+ }
+
+ // Meshes
+ var meshes = new Dictionary();
+ foreach (var sceneMesh in scene.Meshes)
+ {
+ if (!sceneMesh.HasVertices)
+ continue;
+
+ var mesh = new MeshContent
+ {
+ Name = sceneMesh.Name
+ };
+
+ // Position vertices are shared at the mesh level
+ foreach (var vert in sceneMesh.Vertices)
+ mesh.Positions.Add(new Vector3(vert.X, vert.Y, vert.Z));
+
+ var geom = new GeometryContent
+ {
+ Name = string.Empty,
+ //Material = materials[sceneMesh.MaterialIndex]
+ };
+
+ // Geometry vertices reference 1:1 with the MeshContent parent,
+ // no indirection is necessary.
+ geom.Vertices.Positions.AddRange(mesh.Positions);
+ geom.Vertices.AddRange(Enumerable.Range(0, sceneMesh.VertexCount));
+ geom.Indices.AddRange(sceneMesh.GetIntIndices());
+
+ // Individual channels go here
+ if (sceneMesh.HasNormals)
+ geom.Vertices.Channels.Add(VertexChannelNames.Normal(), ToXna(sceneMesh.Normals));
+
+ for (var i = 0; i < sceneMesh.TextureCoordsChannelCount; i++)
+ geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(i),
+ ToXnaVector2(sceneMesh.GetTextureCoords(i)));
+
+ mesh.Geometry.Add(geom);
+ rootNode.Children.Add(mesh);
+ meshes.Add(sceneMesh, mesh);
+ }
+
+ // Bones
+ var bones = new Dictionary();
+ var hierarchyNodes = scene.RootNode.Children.SelectDeep(n => n.Children).ToList();
+ foreach (var node in hierarchyNodes)
+ {
+ var bone = new BoneContent
+ {
+ Name = node.Name,
+ Transform = Matrix.Transpose(ToXna(node.Transform))
+ };
+
+ if (node.Parent == scene.RootNode)
+ rootNode.Children.Add(bone);
+ else
+ {
+ var parent = bones[node.Parent];
+ parent.Children.Add(bone);
+ }
+
+ // Copy the bone's name to the MeshContent - this appears to be
+ // the way it comes out of XNA's FBXImporter.
+ foreach (var meshIndex in node.MeshIndices)
+ meshes[scene.Meshes[meshIndex]].Name = node.Name;
+
+ bones.Add(node, bone);
+ }
+
+ return rootNode;
+ }
+
+ public static Matrix ToXna(Matrix4x4 matrix)
+ {
+ var result = Matrix.Identity;
+
+ result.M11 = matrix.A1;
+ result.M12 = matrix.A2;
+ result.M13 = matrix.A3;
+ result.M14 = matrix.A4;
+
+ result.M21 = matrix.B1;
+ result.M22 = matrix.B2;
+ result.M23 = matrix.B3;
+ result.M24 = matrix.B4;
+
+ result.M31 = matrix.C1;
+ result.M32 = matrix.C2;
+ result.M33 = matrix.C3;
+ result.M34 = matrix.C4;
+
+ result.M41 = matrix.D1;
+ result.M42 = matrix.D2;
+ result.M43 = matrix.D3;
+ result.M44 = matrix.D4;
+
+ return result;
+ }
+
+ public static Vector2[] ToXna(Vector2D[] vectors)
+ {
+ var result = new Vector2[vectors.Length];
+ for (var i = 0; i < vectors.Length; i++)
+ result[i] = new Vector2(vectors[i].X, vectors[i].Y);
+
+ return result;
+ }
+
+ public static Vector2[] ToXnaVector2(Vector3D[] vectors)
+ {
+ var result = new Vector2[vectors.Length];
+ for (var i = 0; i < vectors.Length; i++)
+ result[i] = new Vector2(vectors[i].X, 1 - vectors[i].Y);
+
+ return result;
+ }
+
+ public static Vector3[] ToXna(Vector3D[] vectors)
+ {
+ var result = new Vector3[vectors.Length];
+ for (var i = 0; i < vectors.Length; i++)
+ result[i] = new Vector3(vectors[i].X, vectors[i].Y, vectors[i].Z);
+
+ return result;
+ }
+ }
+
+ public static class EnumerableExtensions
{
///
- /// Initializes a new instance of FbxImporter.
+ /// Returns each element of a tree structure in heriarchical order.
///
- public FbxImporter()
+ /// The enumerated type.
+ /// The enumeration to traverse.
+ /// A function which returns the children of the element.
+ /// An IEnumerable whose elements are in tree structure heriarchical order.
+ public static IEnumerable SelectDeep(this IEnumerable source, Func> selector)
{
+ var stack = new Stack(source.Reverse());
+ while (stack.Count > 0)
+ {
+ // Return the next item on the stack.
+ var item = stack.Pop();
+ yield return item;
+
+ // Get the children from this item.
+ var children = selector(item);
+
+ // If we have no children then skip it.
+ if (children == null)
+ continue;
+
+ // We're using a stack, so we need to push the
+ // children on in reverse to get the correct order.
+ foreach (var child in children.Reverse())
+ stack.Push(child);
+ }
}
///
- /// Called by the XNA Framework when importing an .fbx file to be used as a game asset. This is the method called by the XNA Framework when an asset is to be imported into an object that can be recognized by the Content Pipeline.
+ /// Returns an enumerable from a single element.
///
- /// Name of a game asset file.
- /// Contains information for importing a game asset, such as a logger interface.
- /// Resulting game asset.
- public override NodeContent Import(string filename, ContentImporterContext context)
+ ///
+ ///
+ ///
+ public static IEnumerable AsEnumerable(this T item)
{
- throw new NotImplementedException();
+ yield return item;
}
}
}
diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/BasicMaterialContent.cs b/MonoGame.Framework.Content.Pipeline/Graphics/BasicMaterialContent.cs
new file mode 100644
index 00000000000..e7b26cd8b8d
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Graphics/BasicMaterialContent.cs
@@ -0,0 +1,25 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+ public class BasicMaterialContent : MaterialContent
+ {
+ public const string AlphaKey = "Alpha";
+ public const string DiffuseColorKey = "DiffuseColor";
+ public const string EmissiveColorKey = "EmissiveColor";
+ public const string SpecularColorKey = "SpecularColor";
+ public const string SpecularPowerKey = "SpecularPower";
+ public const string TextureKey = "Texture";
+ public const string VertexColorEnabledKey = "VertexColorEnabled";
+
+ public float? Alpha { get; set; }
+ public Color? DiffuseColor { get; set; }
+ public Color? EmissiveColor { get; set; }
+ public Color? SpecularColor { get; set; }
+ public float? SpecularPower { get; set; }
+ public ExternalReference Texture { get; set; }
+ public bool? VertexColorEnabled { get; set; }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/IndexCollection.cs b/MonoGame.Framework.Content.Pipeline/Graphics/IndexCollection.cs
index d25d32e43a7..5f941b0418b 100644
--- a/MonoGame.Framework.Content.Pipeline/Graphics/IndexCollection.cs
+++ b/MonoGame.Framework.Content.Pipeline/Graphics/IndexCollection.cs
@@ -17,5 +17,11 @@ public sealed class IndexCollection : Collection
public IndexCollection()
{
}
+
+ internal void AddRange(int[] indices)
+ {
+ foreach (var t in indices)
+ Add(t);
+ }
}
}
diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs b/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs
index ec02ab43c15..b35e3e9e570 100644
--- a/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs
+++ b/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs
@@ -31,7 +31,7 @@ public sealed class IndirectPositionCollection : IList, ICollection
/// true if this object is read-only; false otherwise.
- bool System.Collections.Generic.ICollection.IsReadOnly { get { return false; } }
+ bool ICollection.IsReadOnly { get { return false; } }
///
/// Initializes a new instance of IndirectPositionCollection.
@@ -89,6 +89,11 @@ void ICollection.Add(Vector3 item)
items.Add(item);
}
+ public void AddRange(IEnumerable collection)
+ {
+ items.AddRange(collection);
+ }
+
///
/// Removes all elements from the collection.
///
diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/VertexContent.cs b/MonoGame.Framework.Content.Pipeline/Graphics/VertexContent.cs
index c4ec657fe80..565eb0cdeee 100644
--- a/MonoGame.Framework.Content.Pipeline/Graphics/VertexContent.cs
+++ b/MonoGame.Framework.Content.Pipeline/Graphics/VertexContent.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+using Microsoft.Xna.Framework.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
@@ -51,7 +52,9 @@ public sealed class VertexContent
///
internal VertexContent()
{
-
+ channels = new VertexChannelCollection(this);
+ positionIndices = new VertexChannel("PositionIndices");
+ positions = new IndirectPositionCollection();
}
///
@@ -62,7 +65,7 @@ internal VertexContent()
/// Index of the new entry. This can be added to the Indices member of the parent.
public int Add(int positionIndex)
{
- throw new NotImplementedException();
+ return positionIndices.Items.Add(positionIndex);
}
///
@@ -72,7 +75,7 @@ public int Add(int positionIndex)
/// Index into the Positions member of the parent.
public void AddRange(IEnumerable positionIndexCollection)
{
- throw new NotImplementedException();
+ positionIndices.InsertRange(positionIndices.Items.Count, positionIndexCollection);
}
///
@@ -82,7 +85,70 @@ public void AddRange(IEnumerable positionIndexCollection)
/// One or more of the vertex channel types are invalid or an unrecognized name was passed to VertexElementUsage.
public VertexBufferContent CreateVertexBuffer()
{
- throw new NotImplementedException();
+ var vertexBuffer = new VertexBufferContent(positions.Count);
+ var stride = SetupVertexDeclaration(vertexBuffer);
+
+ // TODO: Verify enough elements in channels to match positions?
+
+ // Write out data in an interleaved fashion each channel at a time, for example:
+ // |------------------------------------------------------------|
+ // |POSITION[0] | NORMAL[0] |TEX0[0] | POSITION[1]| NORMAL[1] |
+ // |-----------------------------------------------|------------|
+ // #0 |111111111111|____________|________|111111111111|____________|
+ // #1 |111111111111|111111111111|________|111111111111|111111111111|
+ // #2 |111111111111|111111111111|11111111|111111111111|111111111111|
+
+ // #0: Write position vertices using stride to skip over the other channels:
+ vertexBuffer.Write(0, stride, positions);
+
+ var channelOffset = VertexBufferContent.SizeOf(typeof(Vector3));
+ foreach (var channel in Channels)
+ {
+ // #N: Fill in the channel within each vertex
+ var channelType = channel.ElementType;
+ vertexBuffer.Write(channelOffset, stride, channelType, channel);
+ channelOffset += VertexBufferContent.SizeOf(channelType);
+ }
+
+ return vertexBuffer;
+ }
+
+ private int SetupVertexDeclaration(VertexBufferContent result)
+ {
+ var offset = 0;
+
+ // We always have a position channel
+ result.VertexDeclaration.VertexElements.Add(new VertexElement(offset, VertexElementFormat.Vector3,
+ VertexElementUsage.Position, 0));
+ offset += VertexElementFormat.Vector3.GetTypeSize();
+
+ // Optional channels
+ foreach (var channel in Channels)
+ {
+ VertexElementFormat format;
+ VertexElementUsage usage;
+
+ // Try to determine the vertex format
+ // TODO: Add support for additional formats as they become testable
+ if (channel.ElementType == typeof(Vector3))
+ format = VertexElementFormat.Vector3;
+ else if (channel.ElementType == typeof(Vector2))
+ format = VertexElementFormat.Vector2;
+ else
+ throw new InvalidContentException("Unrecognized vertex content type.");
+
+ // Try to determine the vertex usage
+ if (!VertexChannelNames.TryDecodeUsage(channel.Name, out usage))
+ throw new InvalidContentException("Unknown vertex element usage.");
+
+ // Try getting the usage index
+ var usageIndex = VertexChannelNames.DecodeUsageIndex(channel.Name);
+
+ result.VertexDeclaration.VertexElements.Add(new VertexElement(offset, format, usage, usageIndex));
+ offset += format.GetTypeSize();
+ result.VertexDeclaration.VertexStride = offset;
+ }
+ return offset;
}
///
@@ -93,7 +159,7 @@ public VertexBufferContent CreateVertexBuffer()
/// Position of new vertex index in the collection.
public void Insert(int index, int positionIndex)
{
- throw new NotImplementedException();
+ positionIndices.Items.Insert(index, positionIndex);
}
///
@@ -104,7 +170,7 @@ public void Insert(int index, int positionIndex)
/// Position of the first element of the inserted range in the collection.
public void InsertRange(int index, IEnumerable positionIndexCollection)
{
- throw new NotImplementedException();
+ positionIndices.InsertRange(index, positionIndexCollection);
}
///
@@ -113,7 +179,10 @@ public void InsertRange(int index, IEnumerable positionIndexCollection)
/// Index of the vertex to be removed.
public void RemoveAt(int index)
{
- throw new NotImplementedException();
+ positionIndices.Items.RemoveAt(index);
+
+ foreach (var channel in channels)
+ channel.Items.RemoveAt(index);
}
///
@@ -123,7 +192,8 @@ public void RemoveAt(int index)
/// Number of indices to remove.
public void RemoveRange(int index, int count)
{
- throw new NotImplementedException();
+ for (var i = index; i < index + count; i++)
+ RemoveAt(i);
}
}
}
diff --git a/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.Windows.csproj b/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.Windows.csproj
index c32b2d4d5ed..1134811bd61 100644
--- a/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.Windows.csproj
+++ b/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.Windows.csproj
@@ -31,6 +31,10 @@
4
+
+ False
+ ..\ThirdParty\Libs\assimp\AssimpNet.dll
+ ..\ThirdParty\Libs\ManagedPVRTC\ManagedPVRTC.dll
@@ -84,6 +88,7 @@
+
@@ -131,6 +136,14 @@
+
+
+
+
+
+
+
+
@@ -162,11 +175,13 @@
+
+
@@ -187,6 +202,8 @@
+
+
@@ -226,6 +243,16 @@
PreserveNewest
+
+
+ Assimp32.dll
+ Always
+
+
+ Assimp64.dll
+ Always
+
+
-
\ No newline at end of file
+
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContent.cs
new file mode 100644
index 00000000000..0630f25f5e8
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContent.cs
@@ -0,0 +1,52 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelBoneContent
+ {
+ private ModelBoneContentCollection _children;
+ private int _index;
+ private string _name;
+ private ModelBoneContent _parent;
+ private Matrix _transform;
+
+ internal ModelBoneContent() { }
+
+ internal ModelBoneContent(string name, int index, Matrix transform, ModelBoneContent parent)
+ {
+ _name = name;
+ _index = index;
+ _transform = transform;
+ _parent = parent;
+ }
+
+ public ModelBoneContentCollection Children
+ {
+ get { return _children; }
+ internal set { _children = value; }
+ }
+
+ public int Index
+ {
+ get { return _index; }
+ }
+
+ public string Name
+ {
+ get { return _name; }
+ }
+
+ public ModelBoneContent Parent
+ {
+ get { return _parent; }
+ }
+
+ public Matrix Transform
+ {
+ get { return _transform; }
+ set { _transform = value; }
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContentCollection.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContentCollection.cs
new file mode 100644
index 00000000000..53eb9c189a3
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelBoneContentCollection.cs
@@ -0,0 +1,16 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelBoneContentCollection : ReadOnlyCollection
+ {
+ internal ModelBoneContentCollection(IList list) : base(list)
+ {
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelContent.cs
new file mode 100644
index 00000000000..d34295b16f4
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelContent.cs
@@ -0,0 +1,41 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelContent
+ {
+ private ModelBoneContentCollection _bones;
+ private ModelMeshContentCollection _meshes;
+ private ModelBoneContent _root;
+
+ internal ModelContent() { }
+
+ internal ModelContent(ModelBoneContent root, IList bones, IList meshes)
+ {
+ _root = root;
+ _bones = new ModelBoneContentCollection(bones);
+ _meshes = new ModelMeshContentCollection(meshes);
+ }
+
+ public ModelBoneContentCollection Bones
+ {
+ get { return _bones; }
+ }
+
+ public ModelMeshContentCollection Meshes
+ {
+ get { return _meshes; }
+ }
+
+ public ModelBoneContent Root
+ {
+ get { return _root; }
+ }
+
+ public object Tag { get; set; }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContent.cs
new file mode 100644
index 00000000000..f36cc5fc0f2
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContent.cs
@@ -0,0 +1,57 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelMeshContent
+ {
+ private BoundingSphere _boundingSphere;
+ private ModelMeshPartContentCollection _meshParts;
+ private string _name;
+ private ModelBoneContent _parentBone;
+ private MeshContent _sourceMesh;
+
+ internal ModelMeshContent() { }
+
+ internal ModelMeshContent(string name, MeshContent sourceMesh, ModelBoneContent parentBone,
+ BoundingSphere boundingSphere, IList meshParts)
+ {
+ _name = name;
+ _sourceMesh = sourceMesh;
+ _parentBone = parentBone;
+ _boundingSphere = boundingSphere;
+ _meshParts = new ModelMeshPartContentCollection(meshParts);
+ }
+
+ public BoundingSphere BoundingSphere
+ {
+ get { return _boundingSphere; }
+ }
+
+ public ModelMeshPartContentCollection MeshParts
+ {
+ get { return _meshParts; }
+ }
+
+ public string Name
+ {
+ get { return _name; }
+ }
+
+ public ModelBoneContent ParentBone
+ {
+ get { return _parentBone; }
+ }
+
+ public MeshContent SourceMesh
+ {
+ get { return _sourceMesh; }
+ }
+
+ public object Tag { get; set; }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContentCollection.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContentCollection.cs
new file mode 100644
index 00000000000..48be477dff8
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshContentCollection.cs
@@ -0,0 +1,17 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelMeshContentCollection : ReadOnlyCollection
+ {
+ internal ModelMeshContentCollection(IList list)
+ : base(list)
+ {
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContent.cs
new file mode 100644
index 00000000000..e36647cdd4d
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContent.cs
@@ -0,0 +1,71 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelMeshPartContent
+ {
+ private IndexCollection _indexBuffer;
+ private MaterialContent _material;
+ private int _numVertices;
+ private int _primitiveCount;
+ private int _startIndex;
+ private VertexBufferContent _vertexBuffer;
+ private int _vertexOffset;
+
+ internal ModelMeshPartContent() { }
+
+ internal ModelMeshPartContent(VertexBufferContent vertexBuffer, IndexCollection indices, int vertexOffset,
+ int numVertices, int startIndex, int primitiveCount)
+ {
+ _vertexBuffer = vertexBuffer;
+ _indexBuffer = indices;
+ _vertexOffset = vertexOffset;
+ _numVertices = numVertices;
+ _startIndex = startIndex;
+ _primitiveCount = primitiveCount;
+ }
+
+ public IndexCollection IndexBuffer
+ {
+ get { return _indexBuffer; }
+ }
+
+ public MaterialContent Material
+ {
+ get { return _material; }
+ set { _material = value; }
+ }
+
+ public int NumVertices
+ {
+ get { return _numVertices; }
+ }
+
+ public int PrimitiveCount
+ {
+ get { return _primitiveCount; }
+ }
+
+ public int StartIndex
+ {
+ get { return _startIndex; }
+ }
+
+ public object Tag { get; set; }
+
+ public VertexBufferContent VertexBuffer
+ {
+ get { return _vertexBuffer; }
+ }
+
+ public int VertexOffset
+ {
+ get { return _vertexOffset; }
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContentCollection.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContentCollection.cs
new file mode 100644
index 00000000000..b3498afcefc
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelMeshPartContentCollection.cs
@@ -0,0 +1,17 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ public sealed class ModelMeshPartContentCollection : ReadOnlyCollection
+ {
+ internal ModelMeshPartContentCollection(IList list)
+ : base(list)
+ {
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs
new file mode 100644
index 00000000000..81c3bead141
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs
@@ -0,0 +1,242 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
+{
+ [ContentProcessor(DisplayName = "Model - MonoGame")]
+ public class ModelProcessor : ContentProcessor
+ {
+ private readonly List _meshes = new List();
+
+ private ContentIdentity _identity;
+ private ContentBuildLogger _logger;
+
+ #region Fields for default values
+
+ private bool _colorKeyEnabled = true;
+ private bool _generateMipmaps = true;
+ private bool _premultiplyTextureAlpha = true;
+ private bool _premultiplyVertexColors = true;
+ private float _scale = 1.0f;
+ private TextureProcessorOutputFormat _textureFormat = TextureProcessorOutputFormat.DXTCompressed;
+
+ #endregion
+
+ public ModelProcessor() { }
+
+ #region Properties
+
+ public virtual Color ColorKeyColor { get; set; }
+
+ [DefaultValue(true)]
+ public virtual bool ColorKeyEnabled
+ {
+ get { return _colorKeyEnabled; }
+ set { _colorKeyEnabled = value; }
+ }
+
+ public virtual MaterialProcessorDefaultEffect DefaultEffect { get; set; }
+
+ [DefaultValue(true)]
+ public virtual bool GenerateMipmaps
+ {
+ get { return _generateMipmaps; }
+ set { _generateMipmaps = value; }
+ }
+
+ public virtual bool GenerateTangentFrames { get; set; }
+
+ [DefaultValue(true)]
+ public virtual bool PremultiplyTextureAlpha
+ {
+ get { return _premultiplyTextureAlpha; }
+ set { _premultiplyTextureAlpha = value; }
+ }
+
+ [DefaultValue(true)]
+ public virtual bool PremultiplyVertexColors
+ {
+ get { return _premultiplyVertexColors; }
+ set { _premultiplyVertexColors = value; }
+ }
+
+ public virtual bool ResizeTexturesToPowerOfTwo { get; set; }
+
+ public virtual float RotationX { get; set; }
+
+ public virtual float RotationY { get; set; }
+
+ public virtual float RotationZ { get; set; }
+
+ [DefaultValue(1.0f)]
+ public virtual float Scale
+ {
+ get { return _scale; }
+ set { _scale = value; }
+ }
+
+ public virtual bool SwapWindingOrder { get; set; }
+
+ [DefaultValue(typeof(TextureProcessorOutputFormat), "DXTCompressed")]
+ public virtual TextureProcessorOutputFormat TextureFormat
+ {
+ get { return _textureFormat; }
+ set { _textureFormat = value; }
+ }
+
+ #endregion
+
+ public override ModelContent Process(NodeContent input, ContentProcessorContext context)
+ {
+ _identity = input.Identity;
+ _logger = context.Logger;
+
+ // Gather all the nodes in tree traversal order.
+ var nodes = input.AsEnumerable().SelectDeep(n => n.Children).ToList();
+
+ var meshes = nodes.FindAll(n => n is MeshContent).Cast().ToList();
+ var geometries = meshes.SelectMany(m => m.Geometry).ToList();
+ var distinctMaterials = geometries.Select(g => g.Material).Distinct().ToList();
+
+ // Loop through all distinct materials, passing them through the conversion method
+ // only once, and then processing all geometries using that material.
+ foreach (var inputMaterial in distinctMaterials)
+ {
+ var geomsWithMaterial = geometries.Where(g => g.Material == inputMaterial).ToList();
+ var material = ConvertMaterial(inputMaterial, context);
+
+ ProcessGeometryUsingMaterial(material, geomsWithMaterial, context);
+ }
+
+ // Hierarchy
+ var bones = nodes.OfType().ToList();
+ var modelBones = new List();
+ for (var i = 0; i < bones.Count; i++)
+ {
+ var bone = bones[i];
+
+ // Find the parent
+ var parentIndex = bones.IndexOf(bone.Parent as BoneContent);
+ ModelBoneContent parent = null;
+ if (parentIndex > -1)
+ parent = modelBones[parentIndex];
+
+ modelBones.Add(new ModelBoneContent(bone.Name, i, bone.Transform, parent));
+ }
+
+ foreach (var bone in modelBones)
+ bone.Children = new ModelBoneContentCollection(modelBones.FindAll(b => b.Parent == bone));
+
+ return new ModelContent(modelBones[0], modelBones, _meshes);
+ }
+
+ protected virtual MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
+ {
+ // Do nothing for now
+ return material;
+ }
+
+ protected virtual void ProcessGeometryUsingMaterial(MaterialContent material,
+ IEnumerable geometryCollection,
+ ContentProcessorContext context)
+ {
+ if (material == null)
+ material = new BasicMaterialContent();
+
+ foreach (var geometry in geometryCollection)
+ {
+ ProcessBasicMaterial(material as BasicMaterialContent, geometry);
+
+ var vertexBuffer = geometry.Vertices.CreateVertexBuffer();
+ var primitiveCount = geometry.Vertices.PositionIndices.Count;
+ var parts = new List
+ {
+ new ModelMeshPartContent(vertexBuffer, geometry.Indices, 0, primitiveCount, 0,
+ primitiveCount / 3)
+ };
+
+ var parent = geometry.Parent;
+ var bounds = BoundingSphere.CreateFromPoints(geometry.Vertices.Positions);
+ _meshes.Add(new ModelMeshContent(parent.Name, geometry.Parent, null, bounds, parts));
+ }
+ }
+
+ protected virtual void ProcessVertexChannel(GeometryContent content,
+ int vertexChannelIndex,
+ ContentProcessorContext context)
+ {
+ // Channels with VertexElementUsage.Color -> Color
+ // Channels[VertexChannelNames.Weights] -> { Byte4 boneIndices, Color boneWeights }
+
+ throw new NotImplementedException();
+ }
+
+ private void ProcessBasicMaterial(BasicMaterialContent basicMaterial, GeometryContent geometry)
+ {
+ if (basicMaterial == null)
+ return;
+
+ // If the basic material specifies a texture, geometry must have coordinates.
+ if (!geometry.Vertices.Channels.Contains(VertexChannelNames.TextureCoordinate(0)))
+ throw new InvalidContentException(
+ "Geometry references material with texture, but no texture coordinates were found.",
+ _identity);
+
+ // Enable vertex color if the geometry has the channel to support it.
+ if (geometry.Vertices.Channels.Contains(VertexChannelNames.Color(0)))
+ basicMaterial.VertexColorEnabled = true;
+ }
+ }
+
+ internal static class ModelEnumerableExtensions
+ {
+ ///
+ /// Returns each element of a tree structure in hierarchical order.
+ ///
+ /// The enumerated type.
+ /// The enumeration to traverse.
+ /// A function which returns the children of the element.
+ /// An IEnumerable whose elements are in tree structure heriarchical order.
+ public static IEnumerable SelectDeep(this IEnumerable source, Func> selector)
+ {
+ var stack = new Stack(source.Reverse());
+ while (stack.Count > 0)
+ {
+ // Return the next item on the stack.
+ var item = stack.Pop();
+ yield return item;
+
+ // Get the children from this item.
+ var children = selector(item);
+
+ // If we have no children then skip it.
+ if (children == null)
+ continue;
+
+ // We're using a stack, so we need to push the
+ // children on in reverse to get the correct order.
+ foreach (var child in children.Reverse())
+ stack.Push(child);
+ }
+ }
+
+ ///
+ /// Returns an enumerable from a single element.
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable AsEnumerable(this T item)
+ {
+ yield return item;
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/VertexBufferContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/VertexBufferContent.cs
index 77898a3bd25..2a4e9b07849 100644
--- a/MonoGame.Framework.Content.Pipeline/Processors/VertexBufferContent.cs
+++ b/MonoGame.Framework.Content.Pipeline/Processors/VertexBufferContent.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
@@ -14,27 +15,29 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
/// This type directly corresponds to the runtime VertexBuffer class, and when a VertexBufferContent object is passed to the content compiler, the vertex data deserializes directly into a VertexBuffer at runtime. VertexBufferContent objects are not directly created by importers. The preferred method is to store vertex data in the more flexible VertexContent class.
public class VertexBufferContent : ContentItem
{
- byte[] vertexData;
- VertexDeclarationContent vertexDeclarationContent;
+ MemoryStream stream;
+ BinaryWriter writer;
///
/// Gets the array containing the raw bytes of the packed vertex data. Use this method to get and set the contents of the vertex buffer.
///
/// Raw data of the packed vertex data.
- public byte[] VertexData { get { return vertexData; } }
+ public byte[] VertexData { get { return stream.ToArray(); } }
///
/// Gets the associated VertexDeclarationContent object.
///
/// The associated VertexDeclarationContent object.
- public VertexDeclarationContent VertexDeclaration { get { return vertexDeclarationContent; } set { vertexDeclarationContent = value; } }
+ public VertexDeclarationContent VertexDeclaration { get; private set; }
///
/// Initializes a new instance of VertexBufferContent.
///
public VertexBufferContent()
{
-
+ stream = new MemoryStream();
+ writer = new BinaryWriter(stream);
+ VertexDeclaration = new VertexDeclarationContent();
}
///
@@ -44,7 +47,9 @@ public VertexBufferContent()
public VertexBufferContent(int size)
: base()
{
-
+ stream = new MemoryStream(size);
+ writer = new BinaryWriter(stream);
+ VertexDeclaration = new VertexDeclarationContent();
}
///
@@ -56,6 +61,15 @@ public VertexBufferContent(int size)
/// type is not a valid value type
public static int SizeOf(Type type)
{
+ if (type == typeof(Vector2))
+ return 8;
+
+ if (type == typeof(Vector3))
+ return 12;
+
+ if (type == typeof(Vector4))
+ return 16;
+
throw new NotSupportedException();
}
@@ -69,7 +83,7 @@ public static int SizeOf(Type type)
/// The specified data type cannot be packed into a vertex buffer.
public void Write(int offset, int stride, IEnumerable data)
{
- throw new NotSupportedException();
+ Write(offset, stride, typeof(T), data);
}
///
@@ -82,7 +96,53 @@ public void Write(int offset, int stride, IEnumerable data)
/// The specified data type cannot be packed into a vertex buffer.
public void Write(int offset, int stride, Type dataType, IEnumerable data)
{
- throw new NotSupportedException();
+ if (dataType == typeof(Vector2))
+ Write(offset, stride, data as IEnumerable);
+ else if (dataType == typeof(Vector3))
+ Write(offset, stride, data as IEnumerable);
+ else if (dataType == typeof(Vector4))
+ Write(offset, stride, data as IEnumerable);
+ else
+ throw new NotSupportedException();
+ }
+
+ private void Write(int offset, int stride, IEnumerable data)
+ {
+ stream.Seek(offset, SeekOrigin.Begin);
+ foreach (var item in data)
+ {
+ var next = stream.Position + stride;
+ writer.Write(item.X);
+ writer.Write(item.Y);
+ stream.Seek(next, SeekOrigin.Begin);
+ }
+ }
+
+ private void Write(int offset, int stride, IEnumerable data)
+ {
+ stream.Seek(offset, SeekOrigin.Begin);
+ foreach (var item in data)
+ {
+ var next = stream.Position + stride;
+ writer.Write(item.X);
+ writer.Write(item.Y);
+ writer.Write(item.Z);
+ stream.Seek(next, SeekOrigin.Begin);
+ }
+ }
+
+ private void Write(int offset, int stride, IEnumerable data)
+ {
+ stream.Seek(offset, SeekOrigin.Begin);
+ foreach (var item in data)
+ {
+ var next = stream.Position + stride;
+ writer.Write(item.X);
+ writer.Write(item.Y);
+ writer.Write(item.Z);
+ writer.Write(item.W);
+ stream.Seek(next, SeekOrigin.Begin);
+ }
}
}
}
diff --git a/MonoGame.Framework.Content.Pipeline/Processors/VertexDeclarationContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/VertexDeclarationContent.cs
index 39f31a2d3e1..3c4d3b86a74 100644
--- a/MonoGame.Framework.Content.Pipeline/Processors/VertexDeclarationContent.cs
+++ b/MonoGame.Framework.Content.Pipeline/Processors/VertexDeclarationContent.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
public class VertexDeclarationContent : ContentItem
{
Collection vertexElements;
- Nullable vertexStride;
+ int? vertexStride;
///
/// Gets the VertexElement object of the vertex declaration.
@@ -26,7 +26,11 @@ public class VertexDeclarationContent : ContentItem
/// The number of bytes from one vertex to the next.
///
/// The stride (in bytes).
- public Nullable VertexStride { get; set; }
+ public int? VertexStride
+ {
+ get { return vertexStride; }
+ set { vertexStride = value; }
+ }
///
/// Initializes a new instance of VertexDeclarationContent.
diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs
index aecb9a37d77..dc37cd101b2 100644
--- a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs
+++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs
@@ -293,8 +293,10 @@ public void WriteExternalReference(ExternalReference reference)
/// This method can be called recursively with a null value.
public void WriteObject(T value)
{
- GetTypeWriter(typeof(Dictionary>));
- WriteObject(value, GetTypeWriter(value.GetType()));
+ if (value == null)
+ Write7BitEncodedInt(0);
+ else
+ WriteObject(value, GetTypeWriter(value.GetType()));
}
///
@@ -473,5 +475,11 @@ public void Write(Vector4 value)
Write(value.Z);
Write(value.W);
}
+
+ internal void Write(BoundingSphere value)
+ {
+ Write(value.Center);
+ Write(value.Radius);
+ }
}
}
diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs
new file mode 100644
index 00000000000..47ff00d499b
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs
@@ -0,0 +1,48 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler
+{
+ [ContentTypeWriter]
+ class IndexBufferWriter : BuiltInContentWriter
+ {
+ protected internal override void Write(ContentWriter output, IndexCollection value)
+ {
+ var shortIndices = value.Count < ushort.MaxValue;
+ output.Write(shortIndices);
+
+ var byteCount = shortIndices
+ ? value.Count * 2
+ : value.Count * 4;
+
+ output.Write(byteCount);
+ if (shortIndices)
+ {
+ foreach (var item in value)
+ output.Write((ushort)item);
+ }
+ else
+ {
+ foreach (var item in value)
+ output.Write(item);
+ }
+ }
+
+ public override string GetRuntimeReader(TargetPlatform targetPlatform)
+ {
+ var type = typeof(ContentReader);
+ var readerType = type.Namespace + ".IndexBufferReader, " + type.Assembly.FullName;
+ return readerType;
+ }
+
+ public override string GetRuntimeType(TargetPlatform targetPlatform)
+ {
+ var type = typeof(ContentReader);
+ var readerType = type.Namespace + ".IndexBufferReader, " + type.AssemblyQualifiedName;
+ return readerType;
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ModelWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ModelWriter.cs
new file mode 100644
index 00000000000..464dd67435b
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ModelWriter.cs
@@ -0,0 +1,78 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler
+{
+ [ContentTypeWriter]
+ class ModelWriter : BuiltInContentWriter
+ {
+ protected internal override void Write(ContentWriter output, ModelContent value)
+ {
+ WriteBones(output, value.Bones);
+
+ output.Write((uint)value.Meshes.Count);
+ foreach (var mesh in value.Meshes)
+ {
+ output.WriteObject(mesh.Name);
+ WriteBoneReference(output, mesh.ParentBone, value.Bones);
+ output.Write(mesh.BoundingSphere);
+ output.WriteObject(mesh.Tag);
+
+ output.Write((uint)mesh.MeshParts.Count);
+ foreach (var part in mesh.MeshParts)
+ {
+ output.Write((uint)part.VertexOffset);
+ output.Write((uint)part.NumVertices);
+ output.Write((uint)part.StartIndex);
+ output.Write((uint)part.PrimitiveCount);
+ output.WriteObject(part.Tag);
+
+ output.WriteSharedResource(part.VertexBuffer);
+ output.WriteSharedResource(part.IndexBuffer);
+ output.WriteSharedResource(part.Material);
+ }
+ }
+
+ WriteBoneReference(output, value.Root, value.Bones);
+ output.WriteObject(value.Tag);
+ }
+
+ private void WriteBones(ContentWriter output, ModelBoneContentCollection bones)
+ {
+ output.Write((uint)bones.Count);
+
+ // Bone properties
+ foreach (var bone in bones)
+ {
+ output.WriteObject(bone.Name);
+ output.Write(bone.Transform);
+ }
+
+ // Hierarchy
+ foreach (var bone in bones)
+ {
+ WriteBoneReference(output, bone.Parent, bones);
+
+ output.Write((uint)bone.Children.Count);
+ foreach (var child in bone.Children)
+ WriteBoneReference(output, child, bones);
+ }
+ }
+
+ private void WriteBoneReference(ContentWriter output, ModelBoneContent bone, ModelBoneContentCollection bones)
+ {
+ var boneCount = bones != null ? bones.Count : 0;
+ var boneId = bone != null
+ ? bone.Index + 1
+ : 0;
+
+ if (boneCount < 255)
+ output.Write((byte)boneId);
+ else
+ output.Write((uint)boneId);
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexBufferWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexBufferWriter.cs
new file mode 100644
index 00000000000..be3112d6d29
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexBufferWriter.cs
@@ -0,0 +1,19 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler
+{
+ [ContentTypeWriter]
+ class VertexBufferWriter : BuiltInContentWriter
+ {
+ protected internal override void Write(ContentWriter output, VertexBufferContent value)
+ {
+ output.WriteRawObject(value.VertexDeclaration);
+ output.Write((uint)(value.VertexData.Length / value.VertexDeclaration.VertexStride));
+ output.Write(value.VertexData);
+ }
+ }
+}
diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexDeclarationWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexDeclarationWriter.cs
new file mode 100644
index 00000000000..454d05d3d1b
--- /dev/null
+++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/VertexDeclarationWriter.cs
@@ -0,0 +1,27 @@
+// MonoGame - Copyright (C) The MonoGame Team
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler
+{
+ [ContentTypeWriter]
+ class VertexDeclarationWriter : BuiltInContentWriter
+ {
+ protected internal override void Write(ContentWriter output, VertexDeclarationContent value)
+ {
+ // If fpr whatever reason there isn't a vertex stride defined, it's going to
+ // cause problems after reading it in, so better to fail early here.
+ output.Write((uint)value.VertexStride.Value);
+ output.Write((uint)value.VertexElements.Count);
+ foreach (var element in value.VertexElements)
+ {
+ output.Write((uint)element.Offset);
+ output.Write((int)element.VertexElementFormat);
+ output.Write((int)element.VertexElementUsage);
+ output.Write((uint)element.UsageIndex);
+ }
+ }
+ }
+}