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); + } + } + } +}