(ded4a3e0a) v0.9.0.7

This commit is contained in:
Joonas Rikkonen
2019-06-25 16:00:44 +03:00
parent e5ae622c77
commit 4a51db77b5
1777 changed files with 421528 additions and 917 deletions
@@ -0,0 +1,32 @@
// 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
{
/// <summary>
/// Represents a compiled Effect.
/// </summary>
public class CompiledEffectContent : ContentItem
{
byte[] effectCode;
/// <summary>
/// Creates a new instance of the CompiledEffectContent class
/// </summary>
/// <param name="effectCode">The compiled effect code.</param>
public CompiledEffectContent(byte[] effectCode)
{
this.effectCode = effectCode;
}
/// <summary>
/// Retrieves the compiled byte code for this shader.
/// </summary>
/// <returns>The compiled bytecode.</returns>
public byte[] GetEffectCode()
{
return effectCode;
}
}
}
@@ -0,0 +1,219 @@
// 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.IO;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Graphics;
#if WINDOWS
using TwoMGFX;
#endif
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Processes a string representation to a platform-specific compiled effect.
/// </summary>
[ContentProcessor(DisplayName = "Effect - MonoGame")]
public class EffectProcessor : ContentProcessor<EffectContent, CompiledEffectContent>
{
EffectProcessorDebugMode debugMode;
string defines;
/// <summary>
/// The debug mode for compiling effects.
/// </summary>
/// <value>The debug mode to use when compiling effects.</value>
public virtual EffectProcessorDebugMode DebugMode { get { return debugMode; } set { debugMode = value; } }
/// <summary>
/// Define assignments for the effect.
/// </summary>
/// <value>A list of define assignments delimited by semicolons.</value>
public virtual string Defines { get { return defines; } set { defines = value; } }
/// <summary>
/// Initializes a new instance of EffectProcessor.
/// </summary>
public EffectProcessor()
{
}
/// <summary>
/// Processes the string representation of the specified effect into a platform-specific binary format using the specified context.
/// </summary>
/// <param name="input">The effect string to be processed.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>A platform-specific compiled binary effect.</returns>
/// <remarks>If you get an error during processing, compilation stops immediately. The effect processor displays an error message. Once you fix the current error, it is possible you may get more errors on subsequent compilation attempts.</remarks>
public override CompiledEffectContent Process(EffectContent input, ContentProcessorContext context)
{
#if WINDOWS
var options = new Options();
options.SourceFile = input.Identity.SourceFilename;
options.Profile = ShaderProfile.ForPlatform(context.TargetPlatform.ToString());
if (options.Profile == null)
throw new InvalidContentException(string.Format("{0} effects are not supported.", context.TargetPlatform), input.Identity);
options.Debug = DebugMode == EffectProcessorDebugMode.Debug;
options.Defines = Defines;
options.OutputFile = context.OutputFilename;
// Parse the MGFX file expanding includes, macros, and returning the techniques.
ShaderResult shaderResult;
try
{
shaderResult = ShaderResult.FromFile(options.SourceFile, options,
new ContentPipelineEffectCompilerOutput(context));
// Add the include dependencies so that if they change
// it will trigger a rebuild of this effect.
foreach (var dep in shaderResult.Dependencies)
context.AddDependency(dep);
}
catch (InvalidContentException)
{
throw;
}
catch (Exception ex)
{
// TODO: Extract good line numbers from mgfx parser!
throw new InvalidContentException(ex.Message, input.Identity, ex);
}
// Create the effect object.
EffectObject effect = null;
var shaderErrorsAndWarnings = string.Empty;
try
{
effect = EffectObject.CompileEffect(shaderResult, out shaderErrorsAndWarnings);
// If there were any additional output files we register
// them so that the cleanup process can manage them.
foreach (var outfile in shaderResult.AdditionalOutputFiles)
context.AddOutputFile(outfile);
}
catch (ShaderCompilerException)
{
// This will log any warnings and errors and throw.
ProcessErrorsAndWarnings(true, shaderErrorsAndWarnings, input, context);
}
// Process any warning messages that the shader compiler might have produced.
ProcessErrorsAndWarnings(false, shaderErrorsAndWarnings, input, context);
// Write out the effect to a runtime format.
CompiledEffectContent result;
try
{
using (var stream = new MemoryStream())
{
using (var writer = new BinaryWriter(stream))
effect.Write(writer, options);
result = new CompiledEffectContent(stream.GetBuffer());
}
}
catch (Exception ex)
{
throw new InvalidContentException("Failed to serialize the effect!", input.Identity, ex);
}
return result;
#else
throw new NotImplementedException();
#endif
}
#if WINDOWS
private class ContentPipelineEffectCompilerOutput : IEffectCompilerOutput
{
private readonly ContentProcessorContext _context;
public ContentPipelineEffectCompilerOutput(ContentProcessorContext context)
{
_context = context;
}
public void WriteWarning(string file, int line, int column, string message)
{
_context.Logger.LogWarning(null, CreateContentIdentity(file, line, column), message);
}
public void WriteError(string file, int line, int column, string message)
{
throw new InvalidContentException(message, CreateContentIdentity(file, line, column));
}
private static ContentIdentity CreateContentIdentity(string file, int line, int column)
{
return new ContentIdentity(file, null, line + "," + column);
}
}
#endif
private static void ProcessErrorsAndWarnings(bool buildFailed, string shaderErrorsAndWarnings, EffectContent input, ContentProcessorContext context)
{
// Split the errors and warnings into individual lines.
var errorsAndWarningArray = shaderErrorsAndWarnings.Split(new[] {"\n", "\r", Environment.NewLine},
StringSplitOptions.RemoveEmptyEntries);
var errorOrWarning = new Regex(@"(.*)\(([0-9]*(,[0-9]+(-[0-9]+)?)?)\)\s*:\s*(.*)", RegexOptions.Compiled);
ContentIdentity identity = null;
var allErrorsAndWarnings = string.Empty;
// Process all the lines.
for (var i = 0; i < errorsAndWarningArray.Length; i++)
{
var match = errorOrWarning.Match(errorsAndWarningArray[i]);
if (!match.Success || match.Groups.Count != 4)
{
// Just log anything we don't recognize as a warning.
if (buildFailed)
allErrorsAndWarnings += errorsAndWarningArray[i] + Environment.NewLine;
else
context.Logger.LogWarning(string.Empty, input.Identity, errorsAndWarningArray[i]);
continue;
}
var fileName = match.Groups[1].Value;
var lineAndColumn = match.Groups[2].Value;
var message = match.Groups[3].Value;
// Try to ensure a good file name for the error message.
if (string.IsNullOrEmpty(fileName))
fileName = input.Identity.SourceFilename;
else if (!File.Exists(fileName))
{
var folder = Path.GetDirectoryName(input.Identity.SourceFilename);
fileName = Path.Combine(folder, fileName);
}
// If we got an exception then we'll be throwing an exception
// below, so just gather the lines to throw later.
if (buildFailed)
{
if (identity == null)
{
identity = new ContentIdentity(fileName, input.Identity.SourceTool, lineAndColumn);
allErrorsAndWarnings = errorsAndWarningArray[i] + Environment.NewLine;
}
else
allErrorsAndWarnings += errorsAndWarningArray[i] + Environment.NewLine;
}
else
{
identity = new ContentIdentity(fileName, input.Identity.SourceTool, lineAndColumn);
context.Logger.LogWarning(string.Empty, identity, message, string.Empty);
}
}
if (buildFailed)
throw new InvalidContentException(allErrorsAndWarnings, identity ?? input.Identity);
}
}
}
@@ -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.
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Specifies how debugging of effects is to be supported in PIX.
/// </summary>
public enum EffectProcessorDebugMode
{
/// <summary>
/// Enables effect debugging when built with Debug profile.
/// </summary>
Auto = 0,
/// <summary>
/// Enables effect debugging for all profiles. Will produce unoptimized shaders.
/// </summary>
Debug = 1,
/// <summary>
/// Disables debugging for all profiles, produce optimized shaders.
/// </summary>
Optimize = 2,
}
}
@@ -0,0 +1,275 @@
// 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.IO;
using System.Linq;
using Microsoft.Win32;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using MonoGame.Utilities;
using Glyph = Microsoft.Xna.Framework.Content.Pipeline.Graphics.Glyph;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
[ContentProcessor(DisplayName = "Sprite Font Description - MonoGame")]
public class FontDescriptionProcessor : ContentProcessor<FontDescription, SpriteFontContent>
{
[DefaultValue(true)]
public virtual bool PremultiplyAlpha { get; set; }
[DefaultValue(typeof(TextureProcessorOutputFormat), "Compressed")]
public virtual TextureProcessorOutputFormat TextureFormat { get; set; }
public FontDescriptionProcessor()
{
PremultiplyAlpha = true;
TextureFormat = TextureProcessorOutputFormat.Compressed;
}
public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context)
{
var output = new SpriteFontContent(input);
var fontFile = FindFont(input.FontName, input.Style.ToString());
if (string.IsNullOrWhiteSpace(fontFile))
{
var directories = new List<string> { Path.GetDirectoryName(input.Identity.SourceFilename) };
var extensions = new string[] { "", ".ttf", ".ttc", ".otf" };
// Add special per platform directories
if (CurrentPlatform.OS == OS.Windows)
directories.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"));
else if (CurrentPlatform.OS == OS.MacOSX)
{
directories.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Fonts"));
directories.Add("/Library/Fonts");
}
foreach (var dir in directories)
{
foreach(var ext in extensions)
{
fontFile = Path.Combine(dir, input.FontName + ext);
if (File.Exists(fontFile))
break;
}
if (File.Exists(fontFile))
break;
}
}
if (!File.Exists(fontFile))
throw new FileNotFoundException("Could not find \"" + input.FontName + "\" font file.");
context.Logger.LogMessage ("Building Font {0}", fontFile);
// Get the platform specific texture profile.
var texProfile = TextureProfile.ForPlatform(context.TargetPlatform);
{
if (!File.Exists(fontFile)) {
throw new Exception(string.Format("Could not load {0}", fontFile));
}
var lineSpacing = 0f;
int yOffsetMin = 0;
var glyphs = ImportFont(input, out lineSpacing, out yOffsetMin, context, fontFile);
// Optimize.
foreach (Glyph glyph in glyphs)
{
GlyphCropper.Crop(glyph);
}
// We need to know how to pack the glyphs.
bool requiresPot, requiresSquare;
texProfile.Requirements(context, TextureFormat, out requiresPot, out requiresSquare);
var face = GlyphPacker.ArrangeGlyphs(glyphs, requiresPot, requiresSquare);
// Adjust line and character spacing.
lineSpacing += input.Spacing;
output.VerticalLineSpacing = (int)lineSpacing;
foreach (var glyph in glyphs)
{
output.CharacterMap.Add(glyph.Character);
var texRect = new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, glyph.Subrect.Width, glyph.Subrect.Height);
output.Glyphs.Add(texRect);
var cropping = new Rectangle(0, (int)(glyph.YOffset - yOffsetMin), (int)glyph.XAdvance, output.VerticalLineSpacing);
output.Cropping.Add(cropping);
// Set the optional character kerning.
if (input.UseKerning)
output.Kerning.Add(new Vector3(glyph.CharacterWidths.A, glyph.CharacterWidths.B, glyph.CharacterWidths.C));
else
output.Kerning.Add(new Vector3(0, texRect.Width, 0));
}
output.Texture.Faces[0].Add(face);
}
if (PremultiplyAlpha)
{
var bmp = output.Texture.Faces[0][0];
var data = bmp.GetPixelData();
var idx = 0;
for (; idx < data.Length; )
{
var r = data[idx];
// Special case of simply copying the R component into the A, since R is the value of white alpha we want
data[idx + 0] = r;
data[idx + 1] = r;
data[idx + 2] = r;
data[idx + 3] = r;
idx += 4;
}
bmp.SetPixelData(data);
}
else
{
var bmp = output.Texture.Faces[0][0];
var data = bmp.GetPixelData();
var idx = 0;
for (; idx < data.Length; )
{
var r = data[idx];
// Special case of simply moving the R component into the A and setting RGB to solid white, since R is the value of white alpha we want
data[idx + 0] = 255;
data[idx + 1] = 255;
data[idx + 2] = 255;
data[idx + 3] = r;
idx += 4;
}
bmp.SetPixelData(data);
}
// Perform the final texture conversion.
texProfile.ConvertTexture(context, output.Texture, TextureFormat, true);
return output;
}
private static Glyph[] ImportFont(FontDescription options, out float lineSpacing, out int yOffsetMin, ContentProcessorContext context, string fontName)
{
// Which importer knows how to read this source font?
IFontImporter importer;
var TrueTypeFileExtensions = new List<string> { ".ttf", ".ttc", ".otf" };
//var BitmapFileExtensions = new List<string> { ".bmp", ".png", ".gif" };
string fileExtension = Path.GetExtension(fontName).ToLowerInvariant();
// if (BitmapFileExtensions.Contains(fileExtension))
// {
// importer = new BitmapImporter();
// }
// else
// {
if (!TrueTypeFileExtensions.Contains(fileExtension))
throw new PipelineException("Unknown file extension " + fileExtension);
importer = new SharpFontImporter();
// Import the source font data.
importer.Import(options, fontName);
lineSpacing = importer.LineSpacing;
yOffsetMin = importer.YOffsetMin;
// Get all glyphs
var glyphs = new List<Glyph>(importer.Glyphs);
// Validate.
if (glyphs.Count == 0)
{
throw new Exception("Font does not contain any glyphs.");
}
// Sort the glyphs
glyphs.Sort((left, right) => left.Character.CompareTo(right.Character));
// Check that the default character is part of the glyphs
if (options.DefaultCharacter != null)
{
bool defaultCharacterFound = false;
foreach (var glyph in glyphs)
{
if (glyph.Character == options.DefaultCharacter)
{
defaultCharacterFound = true;
break;
}
}
if (!defaultCharacterFound)
{
throw new InvalidOperationException("The specified DefaultCharacter is not part of this font.");
}
}
return glyphs.ToArray();
}
private string FindFont(string name, string style)
{
if (CurrentPlatform.OS == OS.Windows)
{
var fontDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts");
var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts", false);
foreach (var font in key.GetValueNames().OrderBy(x => x))
{
if (font.StartsWith(name, StringComparison.OrdinalIgnoreCase))
{
var fontPath = key.GetValue(font).ToString();
return Path.IsPathRooted(fontPath) ? fontPath : Path.Combine(fontDirectory, fontPath);
}
}
}
else if (CurrentPlatform.OS == OS.Linux)
{
string s, e;
ExternalTool.Run("/bin/bash", string.Format("-c \"fc-match -f '%{{file}}:%{{family}}\\n' '{0}:style={1}'\"", name, style), out s, out e);
s = s.Trim();
var split = s.Split(':');
if (split.Length < 2)
return string.Empty;
// check font family, fontconfig might return a fallback
if (split[1].Contains(","))
{
// this file defines multiple family names
var families = split[1].Split(',');
foreach (var f in families)
{
if (f.ToLowerInvariant() == name.ToLowerInvariant())
return split[0];
}
// didn't find it
return string.Empty;
}
else
{
if (split[1].ToLowerInvariant() != name.ToLowerInvariant())
return string.Empty;
}
return split[0];
}
return String.Empty;
}
}
}
@@ -0,0 +1,164 @@
// 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 Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
[ContentProcessorAttribute(DisplayName="Font Texture - MonoGame")]
public class FontTextureProcessor : ContentProcessor<Texture2DContent, SpriteFontContent>
{
private Color transparentPixel = Color.Magenta;
[DefaultValue(' ')]
public virtual char FirstCharacter { get; set; }
[DefaultValue (true)]
public virtual bool PremultiplyAlpha { get; set; }
public virtual TextureProcessorOutputFormat TextureFormat { get; set; }
public FontTextureProcessor ()
{
FirstCharacter = ' ';
PremultiplyAlpha = true;
}
protected virtual char GetCharacterForIndex (int index)
{
return (char)(((int)FirstCharacter) + index);
}
private List<Glyph> ExtractGlyphs(PixelBitmapContent<Color> bitmap)
{
var glyphs = new List<Glyph>();
var regions = new List<Rectangle>();
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
if (bitmap.GetPixel(x, y) != transparentPixel)
{
// if we don't have a region that has this pixel already
var re = regions.Find(r => {
return r.Contains(x, y);
});
if (re == Rectangle.Empty)
{
// we have found the top, left of a image.
// we now need to scan for the 'bounds'
int top = y;
int bottom = y;
int left = x;
int right = x;
while (bitmap.GetPixel(right, bottom) != transparentPixel)
right++;
while (bitmap.GetPixel(left, bottom) != transparentPixel)
bottom++;
// we got a glyph :)
regions.Add(new Rectangle(left, top, right - left, bottom - top));
x = right;
}
else
{
x += re.Width;
}
}
}
}
for (int i = 0; i < regions.Count; i++)
{
var rect = regions[i];
var newBitmap = new PixelBitmapContent<Color>(rect.Width, rect.Height);
BitmapContent.Copy(bitmap, rect, newBitmap, new Rectangle(0, 0, rect.Width, rect.Height));
var glyph = new Glyph(GetCharacterForIndex (i), newBitmap);
glyph.CharacterWidths.B = glyph.Bitmap.Width;
glyphs.Add(glyph);
//newbitmap.Save (GetCharacterForIndex(i)+".png", System.Drawing.Imaging.ImageFormat.Png);
}
return glyphs ;
}
public override SpriteFontContent Process(Texture2DContent input, ContentProcessorContext context)
{
var output = new SpriteFontContent();
// extract the glyphs from the texture and map them to a list of characters.
// we need to call GtCharacterForIndex for each glyph in the Texture to
// get the char for that glyph, by default we start at ' ' then '!' and then ASCII
// after that.
BitmapContent face = input.Faces[0][0];
SurfaceFormat faceFormat;
face.TryGetFormat(out faceFormat);
if (faceFormat != SurfaceFormat.Color)
{
var colorFace = new PixelBitmapContent<Color>(face.Width, face.Height);
BitmapContent.Copy(face, colorFace);
face = colorFace;
}
var glyphs = ExtractGlyphs((PixelBitmapContent<Color>)face);
// Optimize.
foreach (var glyph in glyphs)
{
GlyphCropper.Crop(glyph);
output.VerticalLineSpacing = Math.Max(output.VerticalLineSpacing, glyph.Subrect.Height);
}
// Get the platform specific texture profile.
var texProfile = TextureProfile.ForPlatform(context.TargetPlatform);
// We need to know how to pack the glyphs.
bool requiresPot, requiresSquare;
texProfile.Requirements(context, TextureFormat, out requiresPot, out requiresSquare);
face = GlyphPacker.ArrangeGlyphs(glyphs.ToArray(), requiresPot, requiresSquare);
foreach (var glyph in glyphs)
{
output.CharacterMap.Add(glyph.Character);
output.Glyphs.Add(new Rectangle (glyph.Subrect.X, glyph.Subrect.Y, glyph.Subrect.Width, glyph.Subrect.Height));
output.Cropping.Add(new Rectangle((int)glyph.XOffset, (int)glyph.YOffset, glyph.Width, glyph.Height));
var abc = glyph.CharacterWidths;
output.Kerning.Add(new Vector3(abc.A, abc.B, abc.C));
}
output.Texture.Faces[0].Add(face);
var bmp = output.Texture.Faces[0][0];
if (PremultiplyAlpha)
{
var data = bmp.GetPixelData();
var idx = 0;
for (; idx < data.Length;)
{
var r = data[idx + 0];
var g = data[idx + 1];
var b = data[idx + 2];
var a = data[idx + 3];
var col = Color.FromNonPremultiplied(r, g, b, a);
data[idx + 0] = col.R;
data[idx + 1] = col.G;
data[idx + 2] = col.B;
data[idx + 3] = col.A;
idx += 4;
}
bmp.SetPixelData(data);
}
// Perform the final texture conversion.
texProfile.ConvertTexture(context, output.Texture, TextureFormat, true);
return output;
}
}
}
@@ -0,0 +1,96 @@
#region File Description
//-----------------------------------------------------------------------------
// LocalizedFontProcessor.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System.ComponentModel;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
#endregion
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Custom processor extends the SpriteFont build process to scan over the resource
/// strings used by the game, automatically adding whatever characters it finds in
/// them to the font. This makes sure the game will always have all the characters
/// it needs, no matter what languages it is localized into, while still producing
/// an efficient font that does not waste space on unnecessary characters. This is
/// especially useful for languages such as Japanese and Korean, which have
/// potentially thousands of different characters, although games typically only
/// use a small fraction of these. Building only the characters we need is far more
/// efficient than if we tried to include the entire CJK character region.
/// </summary>
[ContentProcessor]
public class LocalizedFontProcessor : ContentProcessor<LocalizedFontDescription,
SpriteFontContent>
{
[DefaultValue(true)]
public virtual bool PremultiplyAlpha { get; set; }
[DefaultValue(typeof(TextureProcessorOutputFormat), "Compressed")]
public virtual TextureProcessorOutputFormat TextureFormat { get; set; }
public LocalizedFontProcessor ()
{
PremultiplyAlpha = true;
TextureFormat = TextureProcessorOutputFormat.Compressed;
}
/// <summary>
/// Converts a font description into SpriteFont format.
/// </summary>
public override SpriteFontContent Process(LocalizedFontDescription input,
ContentProcessorContext context)
{
// Scan each .resx file in turn.
foreach (string resourceFile in input.ResourceFiles)
{
string absolutePath = Path.GetFullPath(resourceFile.Replace ('\\', Path.DirectorySeparatorChar).Replace ('/', Path.DirectorySeparatorChar));
// Make sure the .resx file really does exist.
if (!File.Exists(absolutePath))
{
throw new InvalidContentException("Can't find " + absolutePath);
}
// Load the .resx data.
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(absolutePath);
// Scan each string from the .resx file.
foreach (XmlNode xmlNode in xmlDocument.SelectNodes("root/data/value"))
{
string resourceString = xmlNode.InnerText;
// Scan each character of the string.
foreach (char usedCharacter in resourceString)
{
if (!input.Characters.Contains (usedCharacter))
input.Characters.Add(usedCharacter);
}
}
// Mark that this font should be rebuilt if the resource file changes.
context.AddDependency(absolutePath);
}
var parameters = new OpaqueDataDictionary ();
parameters.Add ("PremultiplyAlpha", PremultiplyAlpha);
parameters.Add ("TextureFormat", TextureFormat);
// After adding the necessary characters, we can use the built in
// FontDescriptionProcessor to do the hard work of building the font for us.
return context.Convert<FontDescription,
SpriteFontContent>(input, "FontDescriptionProcessor", parameters);
}
}
}
@@ -0,0 +1,240 @@
// 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 System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Provides methods and properties for maintaining a collection of named texture references.
/// </summary>
/// <remarks>In addition to texture references, opaque data values are stored in the OpaqueData property of the base class.</remarks>
[ContentProcessor(DisplayName = "Material - MonoGame")]
public class MaterialProcessor : ContentProcessor<MaterialContent, MaterialContent>
{
Color colorKeyColor;
bool colorKeyEnabled;
MaterialProcessorDefaultEffect defaultEffect;
bool generateMipmaps;
bool premultiplyTextureAlpha;
bool resizeTexturesToPowerOfTwo;
TextureProcessorOutputFormat textureFormat;
/// <summary>
/// Gets or sets the color value to replace with transparent black.
/// </summary>
/// <value>Color value of the material to replace with transparent black.</value>
[DefaultValue(typeof(Color), "255, 0, 255, 255")]
[DisplayName("Color Key Color")]
[Description("If the texture is color-keyed, pixels of this color are replaced with transparent black.")]
public virtual Color ColorKeyColor { get { return colorKeyColor; } set { colorKeyColor = value; } }
/// <summary>
/// Specifies whether color keying of a texture is enabled.
/// </summary>
/// <value>true if color keying is enabled; false otherwise.</value>
[DefaultValue(true)]
[DisplayName("Color Key Enabled")]
[Description("If enabled, the source texture is color-keyed. Pixels matching the value of \"Color Key Color\" are replaced with transparent black.")]
public virtual bool ColorKeyEnabled { get { return colorKeyEnabled; } set { colorKeyEnabled = value; } }
/// <summary>
/// The default effect type for this instance of MaterialProcessor.
/// </summary>
/// <value>The default effect type.</value>
/// <remarks>When MaterialProcessor is instantiated, DefaultEffect is set to default to BasicEffect Class.</remarks>
[DefaultValue(typeof(MaterialProcessorDefaultEffect), "BasicEffect")]
[DisplayName("Default Effect")]
[Description("The default effect type.")]
public virtual MaterialProcessorDefaultEffect DefaultEffect { get { return defaultEffect; } set { defaultEffect = value; } }
/// <summary>
/// Specifies if a full chain of mipmaps are generated from the source material. Existing mipmaps of the material are not replaced.
/// </summary>
/// <value>true if mipmap generation is enabled; false otherwise.</value>
[DefaultValue(false)]
[DisplayName("Generate Mipmaps")]
[Description("If enabled, a full chain of mipmaps are generated from the source material. Existing mipmaps of the material are not replaced.")]
public virtual bool GenerateMipmaps { get { return generateMipmaps; } set { generateMipmaps = value; } }
/// <summary>
/// Specifies whether alpha premultiply of textures is enabled.
/// </summary>
/// <value>true if alpha premultiply is enabled; false otherwise.</value>
[DefaultValue(true)]
[DisplayName("Premultiply Alpha")]
[Description("If enabled, the texture is converted to premultiplied alpha format.")]
public virtual bool PremultiplyTextureAlpha { get { return premultiplyTextureAlpha; } set { premultiplyTextureAlpha = value; } }
/// <summary>
/// Specifies whether resizing of a material is enabled. Typically used to maximize compatability with a graphics card because many graphics cards do not support a material size that is not a power of two. If ResizeTexturesToPowerOfTwo is enabled, the material is resized to the next largest power of two.
/// </summary>
/// <value>true if resizing is enabled; false otherwise.</value>
[DefaultValue(true)]
[DisplayName("Resize to Power of Two")]
[Description("If enabled, the texture is resized to the next largest power of two, maximizing compatibility. Many graphics cards do not support texture sizes that are not a power of two.")]
public virtual bool ResizeTexturesToPowerOfTwo { get { return resizeTexturesToPowerOfTwo; } set { resizeTexturesToPowerOfTwo = value; } }
/// <summary>
/// Specifies the texture format of output materials. Materials can either be left unchanged from the source asset, converted to a corresponding Color, or compressed using the appropriate DxtCompressed format.
/// </summary>
/// <value>The texture format of the output.</value>
[DefaultValue(typeof(TextureProcessorOutputFormat), "Color")]
[DisplayName("Texture Format")]
[Description("Specifies the SurfaceFormat type of processed textures. Textures can either remain unchanged from the source asset, converted to the Color format, or DXT compressed.")]
public virtual TextureProcessorOutputFormat TextureFormat { get { return textureFormat; } set { textureFormat = value; } }
/// <summary>
/// Initializes a new instance of the MaterialProcessor class.
/// </summary>
public MaterialProcessor()
{
colorKeyColor = Color.Magenta;
colorKeyEnabled = true;
defaultEffect = MaterialProcessorDefaultEffect.BasicEffect;
generateMipmaps = false;
premultiplyTextureAlpha = true;
resizeTexturesToPowerOfTwo = true;
textureFormat = TextureProcessorOutputFormat.Color;
}
/// <summary>
/// Builds effect content.
/// </summary>
/// <param name="effect">An external reference to the effect content.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>A platform-specific compiled binary effect.</returns>
/// <remarks>If the input to process is of type EffectMaterialContent, this function will be called to request that the EffectContent be built. The EffectProcessor is used to process the EffectContent. Subclasses of MaterialProcessor can override this function to modify the parameters used to build EffectContent. For example, a different version of this function could request a different processor for the EffectContent.</remarks>
protected virtual ExternalReference<CompiledEffectContent> BuildEffect(ExternalReference<EffectContent> effect, ContentProcessorContext context)
{
return context.BuildAsset<EffectContent, CompiledEffectContent>(effect, "EffectProcessor");
}
/// <summary>
/// Builds texture content.
/// </summary>
/// <param name="textureName">The name of the texture. This should correspond to the key used to store the texture in Textures.</param>
/// <param name="texture">The asset to build. This should be a member of Textures.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>The built texture content.</returns>
/// <remarks>textureName can be used to determine which processor to use. For example, if a texture is being used as a normal map, the user may not want to use the ModelTextureProcessor on it, which compresses textures.</remarks>
protected virtual ExternalReference<TextureContent> BuildTexture(string textureName, ExternalReference<TextureContent> texture, ContentProcessorContext context)
{
var parameters = new OpaqueDataDictionary();
parameters.Add("ColorKeyColor", ColorKeyColor);
parameters.Add("ColorKeyEnabled", ColorKeyEnabled);
parameters.Add("GenerateMipmaps", GenerateMipmaps);
parameters.Add("PremultiplyAlpha", PremultiplyTextureAlpha);
parameters.Add("ResizeToPowerOfTwo", ResizeTexturesToPowerOfTwo);
parameters.Add("TextureFormat", TextureFormat);
return context.BuildAsset<TextureContent, TextureContent>(texture, "TextureProcessor", parameters, "TextureImporter", null);
}
/// <summary>
/// Builds the texture and effect content for the material.
/// </summary>
/// <param name="input">The material content to build.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>The built material.</returns>
/// <remarks>If the MaterialContent is of type EffectMaterialContent, a build is requested for Effect, and validation will be performed on the OpaqueData to ensure that all parameters are valid input to SetValue or SetValueTranspose. If the MaterialContent is a BasicMaterialContent, no validation will be performed on OpaqueData. Process requests builds for all textures in Textures, unless the MaterialContent is of type BasicMaterialContent, in which case a build will only be requested for DiffuseColor. The textures in Textures will be ignored.</remarks>
public override MaterialContent Process(MaterialContent input, ContentProcessorContext context)
{
// Apply specified default effect.
if (input is BasicMaterialContent && DefaultEffect != MaterialProcessorDefaultEffect.BasicEffect)
{
var newMaterial = CreateDefaultMaterial(DefaultEffect);
// Preserve material properties.
newMaterial.Name = input.Name;
newMaterial.Identity = input.Identity;
foreach (var item in input.OpaqueData)
newMaterial.OpaqueData.Add(item.Key, item.Value);
foreach (var item in input.Textures)
newMaterial.Textures.Add(item.Key, item.Value);
input = newMaterial;
}
// Docs say that if it's a basic effect, only build the diffuse texture.
var basic = input as BasicMaterialContent;
if (basic != null)
{
ExternalReference<TextureContent> texture;
if (basic.Textures.TryGetValue(BasicMaterialContent.TextureKey, out texture))
basic.Texture = BuildTexture(texture.Filename, texture, context);
return basic;
}
// Build custom effects
var effectMaterial = input as EffectMaterialContent;
if (effectMaterial != null && effectMaterial.CompiledEffect == null)
{
if (effectMaterial.Effect == null)
throw new PipelineException("EffectMaterialContent.Effect or EffectMaterialContent.CompiledEffect should be set for materials with a custom effect.");
effectMaterial.CompiledEffect = BuildEffect(effectMaterial.Effect, context);
// TODO: Docs say to validate OpaqueData for SetValue/SetValueTranspose
// Does that mean to match up with effect param names??
}
// Build all textures
var keys = new List<string>(input.Textures.Keys);
foreach (string key in keys)
{
var texture = input.Textures[key];
var builtTexture = BuildTexture(texture.Filename, texture, context);
input.Textures[key] = builtTexture;
}
return input;
}
/// <summary>
/// Helper method which returns the material for a default effect.
/// </summary>
/// <returns>A material.</returns>
public static MaterialContent CreateDefaultMaterial(MaterialProcessorDefaultEffect effect)
{
switch (effect)
{
case MaterialProcessorDefaultEffect.BasicEffect:
return new BasicMaterialContent();
case MaterialProcessorDefaultEffect.SkinnedEffect:
return new SkinnedMaterialContent();
case MaterialProcessorDefaultEffect.EnvironmentMapEffect:
return new EnvironmentMapMaterialContent();
case MaterialProcessorDefaultEffect.DualTextureEffect:
return new DualTextureMaterialContent();
case MaterialProcessorDefaultEffect.AlphaTestEffect:
return new AlphaTestMaterialContent();
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Helper method which returns the default effect for a material.
/// </summary>
/// <returns>The default effect.</returns>
public static MaterialProcessorDefaultEffect GetDefaultEffect(MaterialContent content)
{
if (content is AlphaTestMaterialContent)
return MaterialProcessorDefaultEffect.AlphaTestEffect;
if (content is BasicMaterialContent)
return MaterialProcessorDefaultEffect.BasicEffect;
if (content is DualTextureMaterialContent)
return MaterialProcessorDefaultEffect.DualTextureEffect;
if (content is EnvironmentMapMaterialContent)
return MaterialProcessorDefaultEffect.EnvironmentMapEffect;
if (content is SkinnedMaterialContent)
return MaterialProcessorDefaultEffect.SkinnedEffect;
throw new ArgumentOutOfRangeException("Unknown material content type!");
}
}
}
@@ -0,0 +1,37 @@
// 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
{
/// <summary>
/// Specifies the default effect type.
/// </summary>
public enum MaterialProcessorDefaultEffect
{
/// <summary>
/// A BasicEffect Class effect.
/// </summary>
BasicEffect = 0,
/// <summary>
/// A SkinnedEffect Class effect.
/// </summary>
SkinnedEffect = 1,
/// <summary>
/// An EnvironmentMapEffect Class effect.
/// </summary>
EnvironmentMapEffect = 2,
/// <summary>
/// A DualTextureEffect Class effect.
/// </summary>
DualTextureEffect = 3,
/// <summary>
/// An AlphaTestEffect Class effect.
/// </summary>
AlphaTestEffect = 4,
}
}
@@ -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; }
}
}
}
@@ -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<ModelBoneContent>
{
internal ModelBoneContentCollection(IList<ModelBoneContent> list) : base(list)
{
}
}
}
@@ -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<ModelBoneContent> bones, IList<ModelMeshContent> 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; }
}
}
@@ -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<ModelMeshPartContent> 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; }
}
}
@@ -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<ModelMeshContent>
{
internal ModelMeshContentCollection(IList<ModelMeshContent> list)
: base(list)
{
}
}
}
@@ -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; }
}
}
}
@@ -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<ModelMeshPartContent>
{
internal ModelMeshPartContentCollection(IList<ModelMeshPartContent> list)
: base(list)
{
}
}
}
@@ -0,0 +1,449 @@
// 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;
using Microsoft.Xna.Framework.Graphics.PackedVector;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
[ContentProcessor(DisplayName = "Model - MonoGame")]
public class ModelProcessor : ContentProcessor<NodeContent, ModelContent>
{
private ContentIdentity _identity;
#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.Compressed;
#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), "Compressed")]
public virtual TextureProcessorOutputFormat TextureFormat
{
get { return _textureFormat; }
set { _textureFormat = value; }
}
#endregion
public override ModelContent Process(NodeContent input, ContentProcessorContext context)
{
_identity = input.Identity;
// Perform the processor transforms.
if (RotationX != 0.0f || RotationY != 0.0f || RotationZ != 0.0f || Scale != 1.0f)
{
var rotX = Matrix.CreateRotationX(MathHelper.ToRadians(RotationX));
var rotY = Matrix.CreateRotationY(MathHelper.ToRadians(RotationY));
var rotZ = Matrix.CreateRotationZ(MathHelper.ToRadians(RotationZ));
var scale = Matrix.CreateScale(Scale);
MeshHelper.TransformScene(input, rotZ * rotX * rotY * scale);
}
// 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<MeshContent>().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);
}
var boneList = new List<ModelBoneContent>();
var meshList = new List<ModelMeshContent>();
var rootNode = ProcessNode(input, null, boneList, meshList, context);
return new ModelContent(rootNode, boneList, meshList);
}
private ModelBoneContent ProcessNode(NodeContent node, ModelBoneContent parent, List<ModelBoneContent> boneList, List<ModelMeshContent> meshList, ContentProcessorContext context)
{
var result = new ModelBoneContent(node.Name, boneList.Count, node.Transform, parent);
boneList.Add(result);
if (node is MeshContent)
meshList.Add(ProcessMesh(node as MeshContent, result, context));
var children = new List<ModelBoneContent>();
foreach (var child in node.Children)
children.Add(ProcessNode(child, result, boneList, meshList, context));
result.Children = new ModelBoneContentCollection(children);
return result;
}
private ModelMeshContent ProcessMesh(MeshContent mesh, ModelBoneContent parent, ContentProcessorContext context)
{
var parts = new List<ModelMeshPartContent>();
var vertexBuffer = new VertexBufferContent();
var indexBuffer = new IndexCollection();
if (GenerateTangentFrames)
{
context.Logger.LogMessage("Generating tangent frames.");
foreach (GeometryContent geom in mesh.Geometry)
{
if (!geom.Vertices.Channels.Contains(VertexChannelNames.Normal(0)))
{
MeshHelper.CalculateNormals(geom, true);
}
if(!geom.Vertices.Channels.Contains(VertexChannelNames.Tangent(0)) ||
!geom.Vertices.Channels.Contains(VertexChannelNames.Binormal(0)))
{
MeshHelper.CalculateTangentFrames(geom, VertexChannelNames.TextureCoordinate(0), VertexChannelNames.Tangent(0),
VertexChannelNames.Binormal(0));
}
}
}
var startVertex = 0;
foreach (var geometry in mesh.Geometry)
{
var vertices = geometry.Vertices;
var vertexCount = vertices.VertexCount;
ModelMeshPartContent partContent;
if (vertexCount == 0)
partContent = new ModelMeshPartContent();
else
{
var geomBuffer = geometry.Vertices.CreateVertexBuffer();
vertexBuffer.Write(vertexBuffer.VertexData.Length, 1, geomBuffer.VertexData);
var startIndex = indexBuffer.Count;
indexBuffer.AddRange(geometry.Indices);
partContent = new ModelMeshPartContent(vertexBuffer, indexBuffer, startVertex, vertexCount, startIndex, geometry.Indices.Count / 3);
// Geoms are supposed to all have the same decl, so just steal one of these
vertexBuffer.VertexDeclaration = geomBuffer.VertexDeclaration;
startVertex += vertexCount;
}
partContent.Material = geometry.Material;
parts.Add(partContent);
}
var bounds = new BoundingSphere();
if (mesh.Positions.Count > 0)
bounds = BoundingSphere.CreateFromPoints(mesh.Positions);
return new ModelMeshContent(mesh.Name, mesh, parent, bounds, parts);
}
protected virtual MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
{
var parameters = new OpaqueDataDictionary();
parameters.Add("ColorKeyColor", ColorKeyColor);
parameters.Add("ColorKeyEnabled", ColorKeyEnabled);
parameters.Add("GenerateMipmaps", GenerateMipmaps);
parameters.Add("PremultiplyTextureAlpha", PremultiplyTextureAlpha);
parameters.Add("ResizeTexturesToPowerOfTwo", ResizeTexturesToPowerOfTwo);
parameters.Add("TextureFormat", TextureFormat);
parameters.Add("DefaultEffect", DefaultEffect);
return context.Convert<MaterialContent, MaterialContent>(material, "MaterialProcessor", parameters);
}
protected virtual void ProcessGeometryUsingMaterial(MaterialContent material,
IEnumerable<GeometryContent> geometryCollection,
ContentProcessorContext context)
{
// If we don't get a material then assign a default one.
if (material == null)
material = MaterialProcessor.CreateDefaultMaterial(DefaultEffect);
// Test requirements from the assigned material.
int textureChannels;
bool vertexWeights = false;
if (material is DualTextureMaterialContent)
{
textureChannels = 2;
}
else if (material is SkinnedMaterialContent)
{
textureChannels = 1;
vertexWeights = true;
}
else if (material is EnvironmentMapMaterialContent)
{
textureChannels = 1;
}
else if (material is AlphaTestMaterialContent)
{
textureChannels = 1;
}
else
{
// Just check for a "Texture" which should cover custom Effects
// and BasicEffect which can have an optional texture.
textureChannels = material.Textures.ContainsKey("Texture") ? 1 : 0;
}
// By default we must set the vertex color property
// to match XNA behavior.
material.OpaqueData["VertexColorEnabled"] = false;
// If we run into a geometry that requires vertex
// color we need a seperate material for it.
var colorMaterial = material.Clone();
colorMaterial.OpaqueData["VertexColorEnabled"] = true;
foreach (var geometry in geometryCollection)
{
// Process the geometry.
for (var i = 0; i < geometry.Vertices.Channels.Count; i++)
ProcessVertexChannel(geometry, i, context);
// Verify we have the right number of texture coords.
for (var i = 0; i < textureChannels; i++)
{
if (!geometry.Vertices.Channels.Contains(VertexChannelNames.TextureCoordinate(i)))
throw new InvalidContentException(
string.Format("The mesh \"{0}\", using {1}, contains geometry that is missing texture coordinates for channel {2}.",
geometry.Parent.Name,
MaterialProcessor.GetDefaultEffect(material),
i),
_identity);
}
// Do we need to enable vertex color?
if (geometry.Vertices.Channels.Contains(VertexChannelNames.Color(0)))
geometry.Material = colorMaterial;
else
geometry.Material = material;
// Do we need vertex weights?
if (vertexWeights)
{
var weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0);
if (!geometry.Vertices.Channels.Contains(weightsName))
throw new InvalidContentException(
string.Format("The skinned mesh \"{0}\" contains geometry without any vertex weights.", geometry.Parent.Name),
_identity);
}
}
}
protected virtual void ProcessVertexChannel(GeometryContent geometry,
int vertexChannelIndex,
ContentProcessorContext context)
{
var channel = geometry.Vertices.Channels[vertexChannelIndex];
// TODO: According to docs, channels with VertexElementUsage.Color -> Color
// Channels[VertexChannelNames.Weights] -> { Byte4 boneIndices, Color boneWeights }
if (channel.Name.StartsWith(VertexChannelNames.Weights()))
ProcessWeightsChannel(geometry, vertexChannelIndex, _identity);
}
private static void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex, ContentIdentity identity)
{
// NOTE: Portions of this code is from the XNA CPU Skinning
// sample under Ms-PL, (c) Microsoft Corporation.
// create a map of Name->Index of the bones
var skeleton = MeshHelper.FindSkeleton(geometry.Parent);
if (skeleton == null)
{
throw new InvalidContentException(
"Skeleton not found. Meshes that contain a Weights vertex channel cannot be processed without access to the skeleton data.",
identity);
}
var boneIndices = new Dictionary<string, int>();
var flattenedBones = MeshHelper.FlattenSkeleton(skeleton);
for (var i = 0; i < flattenedBones.Count; i++)
boneIndices.Add(flattenedBones[i].Name, i);
var vertexChannel = geometry.Vertices.Channels[vertexChannelIndex];
var inputWeights = vertexChannel as VertexChannel<BoneWeightCollection>;
if (inputWeights == null)
{
throw new InvalidContentException(
string.Format(
"Vertex channel \"{0}\" is the wrong type. It has element type {1}. Type {2} is expected.",
vertexChannel.Name,
vertexChannel.ElementType.FullName,
"Microsoft.Xna.Framework.Content.Pipeline.Graphics.BoneWeightCollection"),
identity);
}
var outputIndices = new Byte4[inputWeights.Count];
var outputWeights = new Vector4[inputWeights.Count];
for (var i = 0; i < inputWeights.Count; i++)
ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i);
// create our new channel names
var usageIndex = VertexChannelNames.DecodeUsageIndex(inputWeights.Name);
var indicesName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex);
var weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex);
// add in the index and weight channels
geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices);
geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights);
// remove the original weights channel
geometry.Vertices.Channels.RemoveAt(vertexChannelIndex);
}
// From the XNA CPU Skinning Sample under Ms-PL, (c) Microsoft Corporation
private static void ConvertWeights(BoneWeightCollection weights, Dictionary<string, int> boneIndices, Byte4[] outIndices, Vector4[] outWeights, int vertexIndex)
{
// we only handle 4 weights per bone
const int maxWeights = 4;
// create some temp arrays to hold our values
var tempIndices = new int[maxWeights];
var tempWeights = new float[maxWeights];
// cull out any extra bones
weights.NormalizeWeights(maxWeights);
// get our indices and weights
for (var i = 0; i < weights.Count; i++)
{
var weight = weights[i];
if (!boneIndices.ContainsKey(weight.BoneName))
{
var errorMessage = string.Format("Bone '{0}' was not found in the skeleton! Skeleton bones are: '{1}'.", weight.BoneName, string.Join("', '", boneIndices.Keys));
throw new Exception(errorMessage);
}
tempIndices[i] = boneIndices[weight.BoneName];
tempWeights[i] = weight.Weight;
}
// zero out any remaining spaces
for (var i = weights.Count; i < maxWeights; i++)
{
tempIndices[i] = 0;
tempWeights[i] = 0;
}
// output the values
outIndices[vertexIndex] = new Byte4(tempIndices[0], tempIndices[1], tempIndices[2], tempIndices[3]);
outWeights[vertexIndex] = new Vector4(tempWeights[0], tempWeights[1], tempWeights[2], tempWeights[3]);
}
}
internal static class ModelEnumerableExtensions
{
/// <summary>
/// Returns each element of a tree structure in hierarchical order.
/// </summary>
/// <typeparam name="T">The enumerated type.</typeparam>
/// <param name="source">The enumeration to traverse.</param>
/// <param name="selector">A function which returns the children of the element.</param>
/// <returns>An IEnumerable whose elements are in tree structure heriarchical order.</returns>
public static IEnumerable<T> SelectDeep<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>(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);
}
}
/// <summary>
/// Returns an enumerable from a single element.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
/// <returns></returns>
public static IEnumerable<T> AsEnumerable<T>(this T item)
{
yield return item;
}
}
}
@@ -0,0 +1,23 @@
// 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.Linq;
using System.Text;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// As the name implies, this processor simply passes data through as-is.
/// </summary>
[ContentProcessor(DisplayName = "No Processing Required")]
public class PassThroughProcessor : ContentProcessor<object, object>
{
public override object Process(object input, ContentProcessorContext context)
{
return input;
}
}
}
@@ -0,0 +1,28 @@
// 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;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Represents a processed Song object.
/// </summary>
public sealed class SongContent
{
internal string fileName;
internal TimeSpan duration;
/// <summary>
/// Creates a new instance of the SongContent class
/// </summary>
/// <param name="fileName">Filename of the song</param>
/// <param name="duration">Duration of the song</param>
internal SongContent(string fileName, TimeSpan duration)
{
this.fileName = fileName;
this.duration = duration;
}
}
}
@@ -0,0 +1,61 @@
// 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 Microsoft.Xna.Framework.Content.Pipeline.Audio;
using System.IO;
using MonoGame.Framework.Content.Pipeline.Builder;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// A custom song processor that processes an intermediate AudioContent type. This type encapsulates the source audio content, producing a Song type that can be used in the game.
/// </summary>
[ContentProcessor(DisplayName = "Song - MonoGame")]
public class SongProcessor : ContentProcessor<AudioContent, SongContent>
{
ConversionQuality _quality = ConversionQuality.Best;
/// <summary>
/// Gets or sets the target format quality of the audio content.
/// </summary>
/// <value>The ConversionQuality of this audio data.</value>
public ConversionQuality Quality
{
get { return _quality; }
set { _quality = value; }
}
/// <summary>
/// Initializes a new instance of SongProcessor.
/// </summary>
public SongProcessor()
{
}
/// <summary>
/// Builds the content for the source audio.
/// </summary>
/// <param name="input">The audio content to build.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>The built audio.</returns>
public override SongContent Process(AudioContent input, ContentProcessorContext context)
{
// The xnb name is the basis for the final song filename.
var songFileName = context.OutputFilename;
// Convert and write out the song media file.
var profile = AudioProfile.ForPlatform(context.TargetPlatform);
var finalQuality = profile.ConvertStreamingAudio(context.TargetPlatform, _quality, input, ref songFileName);
// Let the pipeline know about the song file so it can clean things up.
context.AddOutputFile(songFileName);
if (_quality != finalQuality)
context.Logger.LogMessage("Failed to convert using \"{0}\" quality, used \"{1}\" quality", _quality, finalQuality);
// Return the XNB song content.
return new SongContent(PathHelper.GetRelativePath(Path.GetDirectoryName(context.OutputFilename) + Path.DirectorySeparatorChar, songFileName), input.Duration);
}
}
}
@@ -0,0 +1,40 @@
// 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.ObjectModel;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Represents a processed sound effect.
/// </summary>
public sealed class SoundEffectContent
{
internal byte[] format;
internal byte[] data;
internal int loopStart;
internal int loopLength;
internal int duration;
/// <summary>
/// Initializes a new instance of the SoundEffectContent class.
/// </summary>
/// <param name="format">The WAV header.</param>
/// <param name="data">The audio waveform data.</param>
/// <param name="loopStart">The start of the loop segment (must be block aligned).</param>
/// <param name="loopLength">The length of the loop segment (must be block aligned).</param>
/// <param name="duration">The duration of the wave file in milliseconds.</param>
internal SoundEffectContent(IEnumerable<byte> format, IEnumerable<byte> data, int loopStart, int loopLength, int duration)
{
this.format = format.ToArray();
this.data = data.ToArray();
this.loopStart = loopStart;
this.loopLength = loopLength;
this.duration = duration;
}
}
}
@@ -0,0 +1,54 @@
// 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 Microsoft.Xna.Framework.Content.Pipeline.Audio;
using System.IO;
using MonoGame.Framework.Content.Pipeline.Builder;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// A sound effect processor that processes an intermediate AudioContent type. This type encapsulates the source audio content, producing a SoundEffect type that can be used in the game.
/// </summary>
[ContentProcessor(DisplayName = "Sound Effect - MonoGame")]
public class SoundEffectProcessor : ContentProcessor<AudioContent, SoundEffectContent>
{
ConversionQuality quality = ConversionQuality.Best;
/// <summary>
/// Gets or sets the target format quality of the audio content.
/// </summary>
/// <value>The ConversionQuality of this audio data.</value>
public ConversionQuality Quality { get { return quality; } set { quality = value; } }
/// <summary>
/// Initializes a new instance of SoundEffectProcessor.
/// </summary>
public SoundEffectProcessor()
{
}
/// <summary>
/// Builds the content for the source audio.
/// </summary>
/// <param name="input">The audio content to build.</param>
/// <param name="context">Context for the specified processor.</param>
/// <returns>The built audio.</returns>
public override SoundEffectContent Process(AudioContent input, ContentProcessorContext context)
{
if (input == null)
throw new ArgumentNullException("input");
if (context == null)
throw new ArgumentNullException("context");
var profile = AudioProfile.ForPlatform(context.TargetPlatform);
var finalQuality = profile.ConvertAudio(context.TargetPlatform, quality, input);
if (quality != finalQuality)
context.Logger.LogMessage("Failed to convert using \"{0}\" quality, used \"{1}\" quality", quality, finalQuality);
return new SoundEffectContent(input.Format.NativeWaveFormat, input.Data, input.LoopStart, input.LoopLength, (int)input.Duration.TotalMilliseconds);
}
}
}
@@ -0,0 +1,51 @@
// 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.Linq;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
public class SpriteFontContent
{
public SpriteFontContent() { }
public SpriteFontContent(FontDescription desc)
{
FontName = desc.FontName;
Style = desc.Style;
FontSize = desc.Size;
CharacterMap = new List<char>(desc.Characters.Count);
VerticalLineSpacing = (int)desc.Spacing; // Will be replaced in the pipeline.
HorizontalSpacing = desc.Spacing;
DefaultCharacter = desc.DefaultCharacter;
}
public string FontName = string.Empty;
FontDescriptionStyle Style = FontDescriptionStyle.Regular;
public float FontSize;
public Texture2DContent Texture = new Texture2DContent();
public List<Rectangle> Glyphs = new List<Rectangle>();
public List<Rectangle> Cropping = new List<Rectangle>();
public List<Char> CharacterMap = new List<Char>();
public int VerticalLineSpacing;
public float HorizontalSpacing;
public List<Vector3> Kerning = new List<Vector3>();
public Nullable<Char> DefaultCharacter;
}
}
@@ -0,0 +1,124 @@
// 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.ComponentModel;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
[ContentProcessor(DisplayName="Texture - MonoGame")]
public class TextureProcessor : ContentProcessor<TextureContent, TextureContent>
{
public TextureProcessor()
{
ColorKeyColor = new Color(255, 0, 255, 255);
ColorKeyEnabled = true;
PremultiplyAlpha = true;
}
[DefaultValueAttribute(typeof(Color), "255,0,255,255")]
public virtual Color ColorKeyColor { get; set; }
[DefaultValueAttribute(true)]
public virtual bool ColorKeyEnabled { get; set; }
public virtual bool GenerateMipmaps { get; set; }
[DefaultValueAttribute(true)]
public virtual bool PremultiplyAlpha { get; set; }
public virtual bool ResizeToPowerOfTwo { get; set; }
public virtual bool MakeSquare { get; set; }
public virtual TextureProcessorOutputFormat TextureFormat { get; set; }
public override TextureContent Process(TextureContent input, ContentProcessorContext context)
{
SurfaceFormat format;
if (input.Faces[0][0].TryGetFormat(out format))
{
// If it is already a compressed format, we cannot do anything else so just return it
if (format.IsCompressedFormat())
return input;
}
if (ColorKeyEnabled || ResizeToPowerOfTwo || MakeSquare || PremultiplyAlpha || GenerateMipmaps)
{
// Convert to floating point format for modifications. Keep the original format for conversion back later on if required.
var originalType = input.Faces[0][0].GetType();
try
{
input.ConvertBitmapType(typeof(PixelBitmapContent<Vector4>));
}
catch (Exception ex)
{
context.Logger.LogImportantMessage("Could not convert input texture for processing. " + ex.ToString());
throw ex;
}
if (GenerateMipmaps)
input.GenerateMipmaps(true);
for (int f = 0; f < input.Faces.Count; ++f)
{
var face = input.Faces[f];
for (int m = 0; m < face.Count; ++m)
{
var bmp = (PixelBitmapContent<Vector4>)face[m];
if (ColorKeyEnabled)
{
bmp.ReplaceColor(ColorKeyColor.ToVector4(), Vector4.Zero);
}
if (ResizeToPowerOfTwo)
{
if (!GraphicsUtil.IsPowerOfTwo(bmp.Width) || !GraphicsUtil.IsPowerOfTwo(bmp.Height) || (MakeSquare && bmp.Height != bmp.Width))
{
var newWidth = GraphicsUtil.GetNextPowerOfTwo(bmp.Width);
var newHeight = GraphicsUtil.GetNextPowerOfTwo(bmp.Height);
if (MakeSquare)
newWidth = newHeight = Math.Max(newWidth, newHeight);
var resized = new PixelBitmapContent<Vector4>(newWidth, newHeight);
BitmapContent.Copy(bmp, resized);
bmp = resized;
}
}
else if (MakeSquare && bmp.Height != bmp.Width)
{
var newSize = Math.Max(bmp.Width, bmp.Height);
var resized = new PixelBitmapContent<Vector4>(newSize, newSize);
BitmapContent.Copy(bmp, resized);
}
if (PremultiplyAlpha)
{
for (int y = 0; y < bmp.Height; ++y)
{
var row = bmp.GetRow(y);
for (int x = 0; x < bmp.Width; ++x)
row[x] = Color.FromNonPremultiplied(row[x]).ToVector4();
}
}
face[m] = bmp;
}
}
// If no change to the surface format was desired, change it back now before it early outs
if (TextureFormat == TextureProcessorOutputFormat.NoChange)
input.ConvertBitmapType(originalType);
}
// Get the texture profile for the platform and let it convert the texture.
var texProfile = TextureProfile.ForPlatform(context.TargetPlatform);
texProfile.ConvertTexture(context, input, TextureFormat, false);
return input;
}
}
}
@@ -0,0 +1,55 @@
// 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
{
/// <summary>
/// Specifies the target output (of type SurfaceFormat) of the texture processor. Used by TextureProcessor.TextureFormat.
/// </summary>
public enum TextureProcessorOutputFormat
{
/// <summary>
/// The SurfaceFormat value, of the input TextureContent object, is converted to Color by the processor. Typically used for 2D graphics and overlays.
/// </summary>
Color,
/// <summary>
/// The SurfaceFormat value, of the input TextureContent object, is converted to an appropriate DXT compression by the processor. If the input texture
/// contains fractional alpha values, it is converted to DXT5 format (8 bits per texel); otherwise it is converted to DXT1 (4 bits per texel). This
/// conversion reduces the resource's size on the graphics card. Typically used for 3D textures such as 3D model textures.
/// </summary>
DxtCompressed,
/// <summary>
/// The SurfaceFormat value, of the input TextureContent object, is not changed by the processor. Typically used for textures processed by an external tool.
/// </summary>
NoChange,
/// <summary>
/// The SurfaceFormat value, of the input TextureContent object, is converted to an appropriate compressed format for the target platform.
/// This can include PVRTC for iOS, DXT for desktop, Windows 8 and Windows Phone 8, and ETC1 or BGRA4444 for Android.
/// </summary>
Compressed,
/// <summary>
/// The pixel depth of the input texture is reduced to BGR565 for opaque textures, otherwise it uses BGRA4444.
/// </summary>
Color16Bit,
/// <summary>
/// The input texture is compressed using ETC1 texture compression. Used on Android platforms.
/// </summary>
Etc1Compressed,
/// <summary>
/// The input texture is compressed using PVR texture compression. Used on iOS and some Android platforms.
/// </summary>
PvrCompressed,
/// <summary>
/// The input texture is compressed using ATI texture compression. Used on some Android platforms.
/// </summary>
AtcCompressed,
}
}
@@ -0,0 +1,117 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Provides methods and properties for managing a design-time vertex buffer that holds packed vertex data.
/// </summary>
/// <remarks>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.</remarks>
public class VertexBufferContent : ContentItem
{
readonly MemoryStream stream;
/// <summary>
/// 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.
/// </summary>
/// <value>Raw data of the packed vertex data.</value>
public byte[] VertexData { get { return stream.ToArray(); } }
/// <summary>
/// Gets and sets the associated VertexDeclarationContent object.
/// </summary>
/// <value>The associated VertexDeclarationContent object.</value>
public VertexDeclarationContent VertexDeclaration { get; set; }
/// <summary>
/// Initializes a new instance of VertexBufferContent.
/// </summary>
public VertexBufferContent()
{
stream = new MemoryStream();
VertexDeclaration = new VertexDeclarationContent();
}
/// <summary>
/// Initializes a new instance of VertexBufferContent of the specified size.
/// </summary>
/// <param name="size">The size of the vertex buffer content, in bytes.</param>
public VertexBufferContent(int size)
: base()
{
stream = new MemoryStream(size);
VertexDeclaration = new VertexDeclarationContent();
}
/// <summary>
/// Gets the size of the specified type, in bytes.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The size of the specified type, in bytes.</returns>
/// <remarks>Call this method to compute offset parameters for the Write method. If the specified
/// data type cannot be packed into a vertex buffer—for example, if type is not a valid value type—a
/// NotSupportedException is thrown.</remarks>
/// <exception cref="NotSupportedException">type is not a valid value type</exception>
public static int SizeOf(Type type)
{
if (!type.IsValueType || type.IsAutoLayout)
throw new NotSupportedException("The vertex type must be a struct and have a fixed layout");
return Marshal.SizeOf(type);
}
/// <summary>
/// Writes additional data into the vertex buffer. Writing begins at the specified byte offset, and each value is spaced according to the specified stride value (in bytes).
/// </summary>
/// <typeparam name="T">Type being written.</typeparam>
/// <param name="offset">Offset to begin writing at.</param>
/// <param name="stride">Stride of the data being written, in bytes.</param>
/// <param name="data">Enumerated collection of data.</param>
/// <exception cref="NotSupportedException">The specified data type cannot be packed into a vertex buffer.</exception>
public void Write<T>(int offset, int stride, IEnumerable<T> data)
{
Write(offset, stride, typeof(T), data);
}
/// <summary>
/// Writes additional data into the vertex buffer. Writing begins at the specified byte offset, and each value is spaced according to the specified stride value (in bytes).
/// </summary>
/// <param name="offset">Offset at which to begin writing.</param>
/// <param name="stride">Stride of the data being written, in bytes.</param>
/// <param name="dataType">The type of data to be written.</param>
/// <param name="data">The data to write.</param>
/// <exception cref="NotSupportedException">The specified data type cannot be packed into a vertex buffer.</exception>
public void Write(int offset, int stride, Type dataType, IEnumerable data)
{
var size = SizeOf(dataType);
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
// NOTE: This is not a very fast way to serialize
// an unknown struct type, but it is reliable.
//
// Still the chances vertex buffer serialization
// being the bottleneck of the content pipeline
// are almost non-existent.
stream.Seek(offset, SeekOrigin.Begin);
foreach (var item in data)
{
var next = stream.Position + stride;
Marshal.StructureToPtr(item, ptr, false);
Marshal.Copy(ptr, bytes, 0, size);
stream.Write(bytes, 0, size);
stream.Seek(next, SeekOrigin.Begin);
}
Marshal.FreeHGlobal(ptr);
}
}
}
@@ -0,0 +1,43 @@
// 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.ObjectModel;
using Microsoft.Xna.Framework.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
/// <summary>
/// Provides methods and properties for maintaining the vertex declaration data of a VertexContent.
/// </summary>
public class VertexDeclarationContent : ContentItem
{
Collection<VertexElement> vertexElements;
int? vertexStride;
/// <summary>
/// Gets the VertexElement object of the vertex declaration.
/// </summary>
/// <value>The VertexElement object of the vertex declaration.</value>
public Collection<VertexElement> VertexElements { get { return vertexElements; } }
/// <summary>
/// The number of bytes from one vertex to the next.
/// </summary>
/// <value>The stride (in bytes).</value>
public int? VertexStride
{
get { return vertexStride; }
set { vertexStride = value; }
}
/// <summary>
/// Initializes a new instance of VertexDeclarationContent.
/// </summary>
public VertexDeclarationContent()
{
vertexElements = new Collection<VertexElement>();
}
}
}
@@ -0,0 +1,32 @@
// 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 MonoGame.Framework.Content.Pipeline.Builder;
using System.IO;
namespace Microsoft.Xna.Framework.Content.Pipeline.Processors
{
[ContentProcessor(DisplayName = "Video - MonoGame")]
public class VideoProcessor : ContentProcessor<VideoContent, VideoContent>
{
public override VideoContent Process(VideoContent input, ContentProcessorContext context)
{
var relative = Path.GetDirectoryName(PathHelper.GetRelativePath(context.OutputDirectory, context.OutputFilename));
var relVideoPath = PathHelper.Normalize(Path.Combine(relative, Path.GetFileName(input.Filename)));
var absVideoPath = PathHelper.Normalize(Path.Combine(context.OutputDirectory, relVideoPath));
// Make sure the output folder for the video exists.
Directory.CreateDirectory(Path.GetDirectoryName(absVideoPath));
// Copy the already encoded video file over
File.Copy(input.Filename, absVideoPath, true);
context.AddOutputFile(absVideoPath);
// Fixup relative path
input.Filename = PathHelper.GetRelativePath(context.OutputFilename, absVideoPath);
return input;
}
}
}