Konstantin Koch 8287c54432 Included the Visual Studio extension and made the necessary changes to make it run.
Replaced the old VS templates with ones that offer more flexiblity.
Started replacing the Content Project for the samples with our custom project type.
Inlcuded a basic not yet working AssimpImporter.
2015-04-08 14:50:03 +02:00

680 lines
30 KiB
C#

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ANX.Framework.Content.Pipeline.Graphics;
using System.ComponentModel;
using ANX.Framework.Graphics.PackedVector;
using ANX.Framework.Graphics;
using ANX.Framework.Content.Pipeline.Helpers;
#endregion
// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license
namespace ANX.Framework.Content.Pipeline.Processors
{
[ContentProcessor(DisplayName = "Model Processor - ANX Framework")]
public class ModelProcessor : ContentProcessor<NodeContent, ModelContent>
{
[DefaultValue(typeof(Color), "255; 0; 255; 255")]
public virtual Color ColorKeyColor { get; set; }
[DefaultValue(true)]
public virtual bool ColorKeyEnabled { get; set; }
[DefaultValue(MaterialProcessorDefaultEffect.BasicEffect)]
public virtual MaterialProcessorDefaultEffect DefaultEffect { get; set; }
[DefaultValue(true)]
public virtual bool GenerateMipmaps { get; set; }
[DefaultValue(false)]
public virtual bool GenerateTangentFrames { get; set; }
[DefaultValue(true)]
public virtual bool PremultiplyTextureAlpha { get; set; }
[DefaultValue(true)]
public virtual bool PremultiplyVertexColors { get; set; }
[DefaultValue(false)]
public virtual bool ResizeTexturesToPowerOfTwo { get; set; }
[DefaultValue(0f)]
public virtual float RotationX { get; set; }
[DefaultValue(0f)]
public virtual float RotationY { get; set; }
[DefaultValue(0f)]
public virtual float RotationZ { get; set; }
[DefaultValue(1f)]
public virtual float Scale { get; set; }
[DefaultValue(false)]
public virtual bool SwapWindingOrder { get; set; }
[DefaultValue(TextureProcessorOutputFormat.DxtCompressed)]
public virtual TextureProcessorOutputFormat TextureFormat { get; set; }
public ModelProcessor()
{
ColorKeyColor = Color.FromNonPremultiplied(255, 0, 255, 255);
ColorKeyEnabled = true;
DefaultEffect = MaterialProcessorDefaultEffect.BasicEffect;
GenerateMipmaps = true;
GenerateTangentFrames = false;
PremultiplyTextureAlpha = true;
PremultiplyVertexColors = true;
ResizeTexturesToPowerOfTwo = false;
RotationX = 0f;
RotationY = 0f;
RotationZ = 0f;
Scale = 1f;
SwapWindingOrder = false;
TextureFormat = TextureProcessorOutputFormat.DxtCompressed;
}
public override ModelContent Process(NodeContent input, ContentProcessorContext context)
{
if (input == null)
throw new ArgumentNullException("input");
if (context == null)
throw new ArgumentNullException("context");
Matrix transform = Matrix.Identity;
if (this.RotationZ != 0f)
transform *= Matrix.CreateRotationZ(this.RotationZ);
if (this.RotationX != 0f)
transform *= Matrix.CreateRotationX(this.RotationX);
if (this.RotationY != 0f)
transform *= Matrix.CreateRotationY(this.RotationY);
if (this.Scale != 1f)
transform *= Matrix.CreateScale(this.Scale);
if (transform != Matrix.Identity)
MeshHelper.TransformScene(input, transform);
Dictionary<NodeContent, ModelBoneContent> convertedBones = new Dictionary<NodeContent, ModelBoneContent>();
var rootBone = MeshHelper.FindSkeleton(input);
if (rootBone == null)
{
//Create a ModelBoneContent representing the rootBone.
SetupParentBoneHierarchy(input, convertedBones);
}
else
ConvertBones(MeshHelper.FlattenSkeleton(rootBone), convertedBones);
var convertedRootBone = convertedBones.First().Value;
var meshes = NodeContentHelper.EnumNodesOfType<MeshContent>(input);
this.ProcessGeometryUsingMaterials(meshes, context);
var convertedMeshes = ConvertMeshes(meshes, context, convertedBones);
return new ModelContent(convertedRootBone, new List<ModelBoneContent>(convertedBones.Values), new List<ModelMeshContent>(convertedMeshes));
}
private IEnumerable<ModelMeshContent> ConvertMeshes(IEnumerable<MeshContent> meshes, ContentProcessorContext context, Dictionary<NodeContent, ModelBoneContent> convertedBones)
{
if (meshes == null)
throw new ArgumentNullException("geometries");
Dictionary<MeshContent, ModelMeshContent> result = new Dictionary<MeshContent, ModelMeshContent>();
ConvertMeshes(meshes, context, result, convertedBones);
return result.Values;
}
private void ConvertMeshes(IEnumerable<MeshContent> meshes, ContentProcessorContext context, Dictionary<MeshContent, ModelMeshContent> convertedMeshes, Dictionary<NodeContent, ModelBoneContent> convertedBones)
{
Dictionary<MaterialContent, MaterialContent> convertedMaterials = new Dictionary<MaterialContent, MaterialContent>();
foreach (var mesh in meshes)
{
ModelMeshContent convertedMesh;
if (!convertedMeshes.TryGetValue(mesh, out convertedMesh))
{
MeshHelper.MergeDuplicatePositions(mesh, 0f);
MeshHelper.MergeDuplicateVertices(mesh);
if (this.GenerateTangentFrames)
{
bool containsTangents = !mesh.Geometry.Any((x) => x.Vertices.Channels.Contains(VertexChannelNames.Tangent(0)));
bool containsBinormals = !mesh.Geometry.Any((x) => x.Vertices.Channels.Contains(VertexChannelNames.Binormal(0)));
if (containsTangents || containsBinormals)
{
string tangentChannelName = containsTangents ? VertexChannelNames.Tangent(0) : null;
string binormalChannelName = containsBinormals ? VertexChannelNames.Binormal(0) : null;
MeshHelper.CalculateTangentFrames(mesh, VertexChannelNames.TextureCoordinate(0), tangentChannelName, binormalChannelName);
}
}
if (this.SwapWindingOrder)
{
MeshHelper.SwapWindingOrder(mesh);
}
MeshHelper.CalculateNormals(mesh, false);
ProcessVertexChannels(mesh, context);
MeshHelper.OptimizeForCache(mesh);
convertedMesh = new ModelMeshContent()
{
Name = mesh.Name,
SourceMesh = mesh,
BoundingSphere = BoundingSphere.CreateFromPoints(mesh.Positions),
};
convertedMesh.ParentBone = SetupParentBoneHierarchy(mesh, convertedBones);
foreach (var geometry in mesh.Geometry)
{
MaterialContent convertedMaterial = null;
if (geometry.Material != null)
{
if (!convertedMaterials.TryGetValue(geometry.Material, out convertedMaterial))
{
convertedMaterial = this.ConvertMaterial(geometry.Material, context);
convertedMaterials.Add(geometry.Material, convertedMaterial);
}
}
var vertexBuffer = geometry.Vertices.CreateVertexBuffer();
IndexCollection indices = new IndexCollection();
indices.AddRange(geometry.Indices);
ModelMeshPartContent convertedGeometry = new ModelMeshPartContent()
{
Material = convertedMaterial,
VertexBuffer = vertexBuffer,
NumVertices = geometry.Vertices.VertexCount,
PrimitiveCount = geometry.Indices.Count / 3,
VertexOffset = 0, //All have custom vertex buffer, so no vertex offset.
StartIndex = 0,
IndexBuffer = indices,
};
convertedMesh.MeshParts.Add(convertedGeometry);
}
convertedMeshes.Add(mesh, convertedMesh);
}
}
}
private ModelBoneContent SetupParentBoneHierarchy(NodeContent node, Dictionary<NodeContent, ModelBoneContent> convertedBones)
{
if (node == null)
return null;
ModelBoneContent convertedBone;
if (convertedBones.TryGetValue(node, out convertedBone))
return convertedBone;
if (node is BoneContent)
{
BoneContent bone = (BoneContent)node;
return ConvertBone(bone, convertedBones);
}
else
{
//Parent of mesh is not a bone, but the structure of the modelContent requires a bone parent for every mesh, so we have to create one and insert it into the hierarchy.
convertedBone = new ModelBoneContent()
{
Name = node.Name,
Transform = node.Transform,
Index = convertedBones.Count,
};
//If another mesh finds us as its parent, it will try to reuse our newly created bone.
convertedBones.Add(node, convertedBone);
convertedBone.Parent = SetupParentBoneHierarchy(node.Parent, convertedBones);
if (convertedBone.Parent != null)
convertedBone.Parent.Children.Add(convertedBone);
return convertedBone;
}
}
private IEnumerable<ModelBoneContent> ConvertBones(IEnumerable<BoneContent> bones)
{
if (bones == null)
throw new ArgumentNullException("bones");
Dictionary<NodeContent, ModelBoneContent> result = new Dictionary<NodeContent, ModelBoneContent>();
ConvertBones(bones, result);
return result.Values;
}
private void ConvertBones(IEnumerable<BoneContent> bones, Dictionary<NodeContent, ModelBoneContent> convertedDict)
{
foreach (var bone in bones)
{
ModelBoneContent convertedBone;
if (!convertedDict.TryGetValue(bone, out convertedBone))
{
convertedBone = new ModelBoneContent()
{
Name = bone.Name,
Transform = bone.Transform,
Index = convertedDict.Count,
};
convertedDict.Add(bone, convertedBone);
if (bone.Parent is BoneContent)
{
convertedBone.Parent = ConvertBone((BoneContent)bone.Parent, convertedDict);
}
BoneContent[] boneChilds = bone.Children.OfType<BoneContent>().ToArray();
ConvertBones(boneChilds, convertedDict);
foreach (var child in boneChilds)
{
var convertedChild = convertedDict[child];
convertedBone.Children.Add(convertedChild);
}
}
}
}
private ModelBoneContent ConvertBone(BoneContent bone, Dictionary<NodeContent, ModelBoneContent> convertedDict)
{
ModelBoneContent result;
if (!convertedDict.TryGetValue(bone, out result))
{
result = new ModelBoneContent()
{
Name = bone.Name,
Transform = bone.Transform
};
//TODO: check on duplicates
//TODO: handle animations
convertedDict.Add(bone, result);
}
return result;
}
/// <summary>
/// Called by the framework when the MaterialContent property of a GeometryContent object is encountered in the input node collection.
/// </summary>
/// <param name="material">The input material content.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>The converted material content.</returns>
protected virtual MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
{
OpaqueDataDictionary opaqueDataDictionary = new OpaqueDataDictionary();
opaqueDataDictionary["ColorKeyColor"] = this.ColorKeyColor;
opaqueDataDictionary["ColorKeyEnabled"] = this.ColorKeyEnabled;
opaqueDataDictionary["DefaultEffect"] = this.DefaultEffect;
opaqueDataDictionary["GenerateMipmaps"] = this.GenerateMipmaps;
opaqueDataDictionary["PremultiplyTextureAlpha"] = this.PremultiplyTextureAlpha;
opaqueDataDictionary["ResizeTexturesToPowerOfTwo"] = this.ResizeTexturesToPowerOfTwo;
opaqueDataDictionary["TextureFormat"] = this.TextureFormat;
return context.Convert<MaterialContent, MaterialContent>(material, typeof(MaterialProcessor).Name, opaqueDataDictionary);
}
/// <summary>
/// Maps the geometries in the meshes to their materials and calls <see cref="ProcessGeometryUsingMaterial"/> with it.
/// </summary>
/// <param name="meshes"></param>
/// <param name="context"></param>
private void ProcessGeometryUsingMaterials(IEnumerable<MeshContent> meshes, ContentProcessorContext context)
{
Dictionary<MaterialContent, List<GeometryContent>> materialGeometryAssoc = new Dictionary<MaterialContent, List<GeometryContent>>();
//We can't add null as key to a dictionary so we must have a seperate list where all the geometries without materials go.
List<GeometryContent> materiallessGeometries = new List<GeometryContent>();
foreach (var mesh in meshes)
{
foreach (var geometry in mesh.Geometry)
{
MaterialContent material = geometry.Material;
if (material != null)
{
List<GeometryContent> materialGeometries;
if (!materialGeometryAssoc.TryGetValue(material, out materialGeometries))
{
materialGeometries = new List<GeometryContent>();
materialGeometryAssoc.Add(material, materialGeometries);
}
materialGeometries.Add(geometry);
}
else
{
materiallessGeometries.Add(geometry);
}
}
}
foreach (var pair in materialGeometryAssoc)
{
this.ProcessGeometryUsingMaterial(pair.Key, pair.Value, context);
}
if (materiallessGeometries.Count > 0)
{
this.ProcessGeometryUsingMaterial(null, materiallessGeometries, context);
}
}
/// <summary>
/// Processes all geometry using a specified material.
/// </summary>
/// <param name="material"></param>
/// <param name="geometryContent">A collection of all the geometry using the specified material.</param>
/// <param name="context"></param>
protected virtual void ProcessGeometryUsingMaterial(MaterialContent material, IEnumerable<GeometryContent> geometryContent, ContentProcessorContext context)
{
if (material == null)
{
material = new BasicMaterialContent();
foreach (var geometry in geometryContent)
{
geometry.Material = material;
}
}
MaterialProcessorDefaultEffect? materialProcessorDefaultEffect = null;
if (material is BasicMaterialContent)
{
materialProcessorDefaultEffect = this.DefaultEffect;
}
else if (material is SkinnedMaterialContent)
{
materialProcessorDefaultEffect = MaterialProcessorDefaultEffect.SkinnedEffect;
}
else if (material is EnvironmentMapMaterialContent)
{
materialProcessorDefaultEffect = MaterialProcessorDefaultEffect.EnvironmentMapEffect;
}
else if (material is DualTextureMaterialContent)
{
materialProcessorDefaultEffect = MaterialProcessorDefaultEffect.DualTextureEffect;
}
else if (material is AlphaTestMaterialContent)
{
materialProcessorDefaultEffect = MaterialProcessorDefaultEffect.AlphaTestEffect;
}
if (materialProcessorDefaultEffect.HasValue)
{
bool vertexColorEnabled = false;
int blendChannelCount = 0;
int textureChannelCount = 0;
switch (materialProcessorDefaultEffect.Value)
{
case MaterialProcessorDefaultEffect.SkinnedEffect:
textureChannelCount = 1;
blendChannelCount = 1;
break;
case MaterialProcessorDefaultEffect.EnvironmentMapEffect:
textureChannelCount = 1;
break;
case MaterialProcessorDefaultEffect.DualTextureEffect:
textureChannelCount = 2;
vertexColorEnabled = true;
break;
case MaterialProcessorDefaultEffect.AlphaTestEffect:
textureChannelCount = 1;
vertexColorEnabled = true;
break;
default:
textureChannelCount = material.Textures.ContainsKey("Texture") ? 1 : 0;
vertexColorEnabled = true;
break;
}
foreach (var geometry in geometryContent)
{
ValidateTextureChannelsExist(geometry, textureChannelCount);
ValidateWeightsChannelsExist(geometry, blendChannelCount);
}
if (vertexColorEnabled)
{
//SetVertexColorEnabled(material, geometryContent);
}
}
}
private void SetVertexColorEnabled(MaterialContent material, IEnumerable<GeometryContent> geometryContent)
{
throw new NotImplementedException();
}
/// <summary>
/// Checks if the wanted number of texture channels exist within the geometry and if not an <see cref="InvalidContentException"/> gets thrown.
/// </summary>
/// <param name="geometry"></param>
/// <param name="channelCount"></param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.InvalidContentException"></exception>
protected void ValidateTextureChannelsExist(GeometryContent geometry, int channelCount)
{
if (geometry == null)
throw new ArgumentNullException("geometry");
var channels = geometry.Vertices.Channels;
for (int i = 0; i < channelCount; i++)
{
if (!channels.Contains(VertexChannelNames.TextureCoordinate(i)))
{
throw new InvalidContentException(string.Format("The mesh \"{0}\", contains geometry that is missing texture coordinates for channel {1}.", geometry.Parent.Name, i), geometry.Identity);
}
}
}
/// <summary>
/// Checks if the wanted number of blend index and blend weight channels exist within the geometry and if not an <see cref="InvalidContentException"/> gets thrown.
/// </summary>
/// <param name="geometry"></param>
/// <param name="channelCount"></param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.InvalidContentException"></exception>
protected void ValidateWeightsChannelsExist(GeometryContent geometry, int channelCount)
{
if (geometry == null)
throw new ArgumentNullException("geometry");
var channels = geometry.Vertices.Channels;
for (int i = 0; i < channelCount; i++)
{
if (!channels.Contains(VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, i)) |
!channels.Contains(VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, i)))
{
throw new InvalidContentException(string.Format("The mesh \"{0}\", contains geometry that is missing vertex weights for channel {1}.", geometry.Parent.Name, i), geometry.Identity);
}
}
}
private void ProcessVertexChannels(MeshContent mesh, ContentProcessorContext context)
{
foreach (var geometry in mesh.Geometry)
{
VertexChannelCollection channels = geometry.Vertices.Channels;
for (int i = 0; i < channels.Count; i++)
{
this.ProcessVertexChannel(geometry, i, context);
}
}
}
/// <summary>
/// Processes geometry content vertex channels at the specified index.
/// </summary>
/// <remarks>
/// This function will be called for each VertexChannel of Vertices found in the input mesh.
/// Subclasses of ModelProcessor can override ProcessVertexChannel to control how vertex data is processed.
/// The default implementation converts VertexElementUsage.Color channels to Color format, and replaces Weights channels with a
/// pair of VertexElementUsage.BlendIndices and VertexElementUsage.BlendIndices channels (using Byte4 format for the
/// VertexElementUsage.BlendWeight, Color for the VertexElementUsage.BlendWeight, and discarding excess weights if there are more
/// than four per vertex).
/// </remarks>
/// <param name="geometry"></param>
/// <param name="vertexChannelIndex"></param>
/// <param name="context"></param>
protected virtual void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context)
{
switch (VertexChannelNames.DecodeBaseName(geometry.Vertices.Channels[vertexChannelIndex].Name))
{
case "Color":
this.ProcessColorChannel(geometry, vertexChannelIndex, context);
break;
case "Weights":
this.ProcessWeightsChannel(geometry, vertexChannelIndex, context);
break;
}
}
/// <summary>
/// Processes the channel inside the geometry as a color channel.
/// <remarks>Gets called from <see cref="ProcessVertexChannel"/>.</remarks>
/// </summary>
/// <param name="geometry"></param>
/// <param name="vertexChannelIndex"></param>
/// <param name="context"></param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.InvalidContentException"></exception>
protected virtual void ProcessColorChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context)
{
if (geometry == null)
throw new ArgumentNullException("geometry");
VertexChannelCollection channels = geometry.Vertices.Channels;
try
{
channels.ConvertChannelContent<Color>(vertexChannelIndex);
}
catch (Exception exc)
{
throw new InvalidContentException(string.Format("Can't convert vertex channel \"{0}\" to element type {0} for color channel.", channels[vertexChannelIndex].Name, typeof(Color)), exc);
}
if (this.PremultiplyVertexColors)
{
VertexChannel<Color> vertexChannel = channels.Get<Color>(vertexChannelIndex);
for (int i = 0; i < vertexChannel.Count; i++)
{
Color color = vertexChannel[i];
vertexChannel[i] = Color.FromNonPremultiplied(color.R, color.G, color.B, color.A);
}
}
}
/// <summary>
/// Processes the channel inside the geometry as a weights channel.
/// <remarks>Gets called from <see cref="ProcessVertexChannel"/>.</remarks>
/// </summary>
/// <param name="geometry"></param>
/// <param name="vertexChannelIndex"></param>
/// <param name="context"></param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.InvalidContentException"></exception>
protected virtual void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context)
{
if (geometry == null)
throw new ArgumentNullException("geometry");
throw new NotImplementedException();
//TODO: implement
/*VertexChannelCollection channels = geometry.Vertices.Channels;
VertexChannel vertexChannel = channels[vertexChannelIndex];
if (!(vertexChannel is VertexChannel<BoneWeightCollection>))
{
throw new InvalidContentException(string.Format("Expected vertex channel \"{0}\" to contain BoneWeightCollection, but found {1}.", vertexChannel.Name, vertexChannel.ElementType));
}
VertexChannel<BoneWeightCollection> boneWeightsChannel = vertexChannel as VertexChannel<BoneWeightCollection>;
Byte4[] indices = new Byte4[boneWeightsChannel.Count];
Vector4[] weights = new Vector4[boneWeightsChannel.Count];
for (int i = 0; i < boneWeightsChannel.Count; i++)
{
BoneWeightCollection boneWeightCollection = boneWeightsChannel[i];
if (boneWeightCollection == null)
{
throw new InvalidContentException(string.Format(CultureInfo.CurrentCulture, Resources.NullVertexChannelEntry, new object[]
{
boneWeightsChannel.Name,
typeof(BoneWeightCollection).Name
}));
}
ModelProcessor.ConvertVertexWeights(boneWeightCollection, indices, weights, i, geometry);
}
int usageIndex = VertexChannelNames.DecodeUsageIndex(boneWeightsChannel.Name);
string blendIndicesText = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);
string blendWeightsText = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);
if (channels.Contains(blendIndicesText))
{
throw new InvalidContentException(string.Format(CultureInfo.CurrentCulture, Resources.ConvertWeightsOutputAlreadyExists, new object[]
{
boneWeightsChannel.Name,
blendIndicesText
}));
}
if (channels.Contains(blendWeightsText))
{
throw new InvalidContentException(string.Format(CultureInfo.CurrentCulture, Resources.ConvertWeightsOutputAlreadyExists, new object[]
{
boneWeightsChannel.Name,
blendWeightsText
}));
}
channels.Insert<Byte4>(vertexChannelIndex + 1, blendIndicesText, indices);
channels.Insert<Vector4>(vertexChannelIndex + 2, blendWeightsText, weights);
channels.RemoveAt(vertexChannelIndex);*/
}
/*private static void ConvertVertexWeights(BoneWeightCollection inputWeights, Byte4[] outputIndices, Vector4[] outputWeights, int vertexIndex, GeometryContent geometry)
{
inputWeights.NormalizeWeights(SkinnedEffect.MaxBonesPerVertex);
for (int i = 0; i < inputWeights.Count; i++)
{
BoneWeight boneWeight = inputWeights[i];
if (!boneIndices.TryGetValue(boneWeight.BoneName, out ModelProcessor.tempIndices[i]))
{
throw new InvalidContentException(string.Format(CultureInfo.CurrentCulture, Resources.VertexHasUnknownBoneName, new object[]
{
boneWeight.BoneName
}), geometry.Parent.Identity);
}
ModelProcessor.tempWeights[i] = boneWeight.Weight;
}
for (int j = inputWeights.Count; j < 4; j++)
{
ModelProcessor.tempIndices[j] = 0;
ModelProcessor.tempWeights[j] = 0f;
}
outputIndices[vertexIndex] = new Byte4((float)ModelProcessor.tempIndices[0], (float)ModelProcessor.tempIndices[1], (float)ModelProcessor.tempIndices[2], (float)ModelProcessor.tempIndices[3]);
outputWeights[vertexIndex] = new Vector4(ModelProcessor.tempWeights[0], ModelProcessor.tempWeights[1], ModelProcessor.tempWeights[2], ModelProcessor.tempWeights[3]);
}*/
}
}