(ded4a3e0a) v0.9.0.7
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
// 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.IO;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates and provides operations, such as format conversions, on the
|
||||
/// source audio. This type is produced by the audio importers and used by audio
|
||||
/// processors to produce compiled audio assets.
|
||||
/// </summary>
|
||||
/// <remarks>Note that AudioContent can load and process audio files that are not supported by the importers.</remarks>
|
||||
public class AudioContent : ContentItem, IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly string _fileName;
|
||||
private readonly AudioFileType _fileType;
|
||||
private ReadOnlyCollection<byte> _data;
|
||||
private TimeSpan _duration;
|
||||
private AudioFormat _format;
|
||||
private int _loopStart;
|
||||
private int _loopLength;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the original source audio file.
|
||||
/// </summary>
|
||||
[ContentSerializer(AllowNull = false)]
|
||||
public string FileName { get { return _fileName; } }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the original source audio file.
|
||||
/// </summary>
|
||||
public AudioFileType FileType { get { return _fileType; } }
|
||||
|
||||
/// <summary>
|
||||
/// The current raw audio data without header information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This changes from the source data to the output data after conversion.
|
||||
/// For MP3 and WMA files this throws an exception to match XNA behavior.
|
||||
/// </remarks>
|
||||
public ReadOnlyCollection<byte> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed || _data == null)
|
||||
throw new InvalidContentException("Could not read the audio data from file \"" + Path.GetFileName(_fileName) + "\".");
|
||||
return _data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the audio data.
|
||||
/// </summary>
|
||||
public TimeSpan Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
return _duration;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current format of the audio data.
|
||||
/// </summary>
|
||||
/// <remarks>This changes from the source format to the output format after conversion.</remarks>
|
||||
public AudioFormat Format
|
||||
{
|
||||
get
|
||||
{
|
||||
return _format;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current loop length in samples.
|
||||
/// </summary>
|
||||
/// <remarks>This changes from the source loop length to the output loop length after conversion.</remarks>
|
||||
public int LoopLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return _loopLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current loop start location in samples.
|
||||
/// </summary>
|
||||
/// <remarks>This changes from the source loop start to the output loop start after conversion.</remarks>
|
||||
public int LoopStart
|
||||
{
|
||||
get
|
||||
{
|
||||
return _loopStart;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AudioContent.
|
||||
/// </summary>
|
||||
/// <param name="audioFileName">Name of the audio source file to be processed.</param>
|
||||
/// <param name="audioFileType">Type of the processed audio: WAV, MP3 or WMA.</param>
|
||||
/// <remarks>Constructs the object from the specified source file, in the format specified.</remarks>
|
||||
public AudioContent(string audioFileName, AudioFileType audioFileType)
|
||||
{
|
||||
_fileName = audioFileName;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the full path to the file.
|
||||
audioFileName = Path.GetFullPath(audioFileName);
|
||||
|
||||
// Use probe to get the details of the file.
|
||||
DefaultAudioProfile.ProbeFormat(audioFileName, out _fileType, out _format, out _duration, out _loopStart, out _loopLength);
|
||||
|
||||
// Looks like XNA only cares about type mismatch when
|
||||
// the type is WAV... else it is ok.
|
||||
if ( (audioFileType == AudioFileType.Wav || _fileType == AudioFileType.Wav) &&
|
||||
audioFileType != _fileType)
|
||||
throw new ArgumentException("Incorrect file type!", "audioFileType");
|
||||
|
||||
// Only provide the data for WAV files.
|
||||
if (audioFileType == AudioFileType.Wav)
|
||||
{
|
||||
byte[] rawData;
|
||||
|
||||
// Must be opened in read mode otherwise it fails to open
|
||||
// read-only files (found in some source control systems)
|
||||
using (var fs = new FileStream(audioFileName, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
rawData = new byte[fs.Length];
|
||||
fs.Read(rawData, 0, rawData.Length);
|
||||
}
|
||||
|
||||
AudioFormat riffAudioFormat;
|
||||
var stripped = DefaultAudioProfile.StripRiffWaveHeader(rawData, out riffAudioFormat);
|
||||
|
||||
if (riffAudioFormat != null)
|
||||
{
|
||||
if ((_format.Format != 2 && _format.Format != 17) && _format.BlockAlign != riffAudioFormat.BlockAlign)
|
||||
throw new InvalidOperationException("Calculated block align does not match RIFF " + _format.BlockAlign + " : " + riffAudioFormat.BlockAlign);
|
||||
if (_format.ChannelCount != riffAudioFormat.ChannelCount)
|
||||
throw new InvalidOperationException("Probed channel count does not match RIFF: " + _format.ChannelCount + ", " + riffAudioFormat.ChannelCount);
|
||||
if (_format.Format != riffAudioFormat.Format)
|
||||
throw new InvalidOperationException("Probed audio format does not match RIFF: " + _format.Format + ", " + riffAudioFormat.Format);
|
||||
if (_format.SampleRate != riffAudioFormat.SampleRate)
|
||||
throw new InvalidOperationException("Probed sample rate does not match RIFF: " + _format.SampleRate + ", " + riffAudioFormat.SampleRate);
|
||||
}
|
||||
|
||||
_data = Array.AsReadOnly(stripped);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = string.Format("Failed to open file {0}. Ensure the file is a valid audio file and is not DRM protected.", Path.GetFileNameWithoutExtension(audioFileName));
|
||||
throw new InvalidContentException(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transcodes the source audio to the target format and quality.
|
||||
/// </summary>
|
||||
/// <param name="formatType">Format to convert this audio to.</param>
|
||||
/// <param name="quality">Quality of the processed output audio. For streaming formats, it can be one of the following: Low (96 kbps), Medium (128 kbps), Best (192 kbps). For WAV formats, it can be one of the following: Low (11kHz ADPCM), Medium (22kHz ADPCM), Best (44kHz PCM)</param>
|
||||
/// <param name="saveToFile">
|
||||
/// The name of the file that the converted audio should be saved into. This is used for SongContent, where
|
||||
/// the audio is stored external to the XNB file. If this is null, then the converted audio is stored in
|
||||
/// the Data property.
|
||||
/// </param>
|
||||
[Obsolete("You should prefer to use AudioProfile.")]
|
||||
public void ConvertFormat(ConversionFormat formatType, ConversionQuality quality, string saveToFile)
|
||||
{
|
||||
// Call the legacy conversion code.
|
||||
DefaultAudioProfile.ConvertToFormat(this, formatType, quality, saveToFile);
|
||||
}
|
||||
|
||||
public void SetData(byte[] data, AudioFormat format, TimeSpan duration, int loopStart, int loopLength)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException("data");
|
||||
if (format == null)
|
||||
throw new ArgumentNullException("format");
|
||||
|
||||
_data = Array.AsReadOnly(data);
|
||||
_format = format;
|
||||
_duration = duration;
|
||||
_loopStart = loopStart;
|
||||
_loopLength = loopLength;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the audio file.
|
||||
/// </summary>
|
||||
public enum AudioFileType
|
||||
{
|
||||
/// <summary>
|
||||
/// The MP3 format
|
||||
/// </summary>
|
||||
Mp3,
|
||||
|
||||
/// <summary>
|
||||
/// The WAV format
|
||||
/// </summary>
|
||||
Wav,
|
||||
|
||||
/// <summary>
|
||||
/// The WMA format
|
||||
/// </summary>
|
||||
Wma,
|
||||
|
||||
/// <summary>
|
||||
/// The Ogg format
|
||||
/// </summary>
|
||||
Ogg,
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates the native audio format (WAVEFORMATEX) information of the audio content.
|
||||
/// </summary>
|
||||
public sealed class AudioFormat
|
||||
{
|
||||
int averageBytesPerSecond;
|
||||
int bitsPerSample;
|
||||
int blockAlign;
|
||||
int channelCount;
|
||||
int format;
|
||||
List<byte> nativeWaveFormat;
|
||||
int sampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the average bytes processed per second.
|
||||
/// </summary>
|
||||
/// <value>Average bytes processed per second.</value>
|
||||
public int AverageBytesPerSecond { get { return averageBytesPerSecond; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit depth of the audio content.
|
||||
/// </summary>
|
||||
/// <value>If the audio has not been processed, the source bit depth; otherwise, the bit depth of the new format.</value>
|
||||
public int BitsPerSample { get { return bitsPerSample; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes per sample block, taking channels into consideration. For example, for 16-bit stereo audio (PCM format), the size of each sample block is 4 bytes.
|
||||
/// </summary>
|
||||
/// <value>Number of bytes, per sample block.</value>
|
||||
public int BlockAlign { get { return blockAlign; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of channels.
|
||||
/// </summary>
|
||||
/// <value>If the audio has not been processed, the source channel count; otherwise, the new channel count.</value>
|
||||
public int ChannelCount { get { return channelCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format of the audio content.
|
||||
/// </summary>
|
||||
/// <value>If the audio has not been processed, the format tag of the source content; otherwise, the new format tag.</value>
|
||||
public int Format { get { return format; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw byte buffer for the format. For non-PCM formats, this buffer contains important format-specific information beyond the basic format information exposed in other properties of the AudioFormat type.
|
||||
/// </summary>
|
||||
/// <value>The raw byte buffer represented in a collection.</value>
|
||||
public ReadOnlyCollection<byte> NativeWaveFormat { get { return nativeWaveFormat.AsReadOnly(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sample rate of the audio content.
|
||||
/// </summary>
|
||||
/// <value>If the audio has not been processed, the source sample rate; otherwise, the new sample rate.</value>
|
||||
public int SampleRate { get { return sampleRate; } }
|
||||
|
||||
internal AudioFormat(
|
||||
int averageBytesPerSecond,
|
||||
int bitsPerSample,
|
||||
int blockAlign,
|
||||
int channelCount,
|
||||
int format,
|
||||
int sampleRate)
|
||||
{
|
||||
this.averageBytesPerSecond = averageBytesPerSecond;
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
this.blockAlign = blockAlign;
|
||||
this.channelCount = channelCount;
|
||||
this.format = format;
|
||||
this.sampleRate = sampleRate;
|
||||
|
||||
this.nativeWaveFormat = this.ConstructNativeWaveFormat();
|
||||
}
|
||||
|
||||
private List<byte> ConstructNativeWaveFormat()
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new BinaryWriter(memory))
|
||||
{
|
||||
writer.Write((short)this.format);
|
||||
writer.Write((short)this.channelCount);
|
||||
writer.Write((int)this.sampleRate);
|
||||
writer.Write((int)this.averageBytesPerSecond);
|
||||
writer.Write((short)this.blockAlign);
|
||||
writer.Write((short)this.bitsPerSample);
|
||||
writer.Write((short)0);
|
||||
|
||||
var bytes = new byte[memory.Position];
|
||||
memory.Seek(0, SeekOrigin.Begin);
|
||||
memory.Read(bytes, 0, bytes.Length);
|
||||
return bytes.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for audio importing, conversion and processing.
|
||||
/// </summary>
|
||||
class AudioHelper
|
||||
{
|
||||
// This array must remain in sync with the ConversionFormat enum.
|
||||
static string[] conversionFormatExtensions = new[] { "wav", "wav", "wma", "xma", "wav", "m4a", "ogg" };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file extension for an audio format.
|
||||
/// </summary>
|
||||
/// <param name="format">The conversion format</param>
|
||||
/// <returns>The file extension for the given conversion format.</returns>
|
||||
static public string GetExtension(ConversionFormat format)
|
||||
{
|
||||
return conversionFormatExtensions[(int)format];
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// 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;
|
||||
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Audio
|
||||
{
|
||||
public abstract class AudioProfile
|
||||
{
|
||||
private static readonly LoadedTypeCollection<AudioProfile> _profiles = new LoadedTypeCollection<AudioProfile>();
|
||||
|
||||
/// <summary>
|
||||
/// Find the profile for this target platform.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform target for audio.</param>
|
||||
/// <returns></returns>
|
||||
public static AudioProfile ForPlatform(TargetPlatform platform)
|
||||
{
|
||||
var profile = _profiles.FirstOrDefault(h => h.Supports(platform));
|
||||
if (profile != null)
|
||||
return profile;
|
||||
|
||||
throw new PipelineException("There is no supported audio profile for the '" + platform + "' platform!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this profile supports audio processing for this platform.
|
||||
/// </summary>
|
||||
public abstract bool Supports(TargetPlatform platform);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the audio content to work on targeted platform.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform to build the audio content for.</param>
|
||||
/// <param name="quality">The suggested audio quality level.</param>
|
||||
/// <param name="content">The audio content to convert.</param>
|
||||
/// <returns>The quality used for conversion which could be different from the suggested quality.</returns>
|
||||
public abstract ConversionQuality ConvertAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the audio content to a streaming format that works on targeted platform.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform to build the audio content for.</param>
|
||||
/// <param name="quality">The suggested audio quality level.</param>
|
||||
/// <param name="content">he audio content to convert.</param>
|
||||
/// <param name="outputFileName"></param>
|
||||
/// <returns>The quality used for conversion which could be different from the suggested quality.</returns>
|
||||
public abstract ConversionQuality ConvertStreamingAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content, ref string outputFileName);
|
||||
|
||||
|
||||
protected static int QualityToSampleRate(ConversionQuality quality, int sourceSampleRate)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case ConversionQuality.Low:
|
||||
return Math.Max(8000, (int)Math.Floor(sourceSampleRate / 2.0));
|
||||
case ConversionQuality.Medium:
|
||||
return Math.Max(8000, (int)Math.Floor((sourceSampleRate / 4.0) * 3));
|
||||
}
|
||||
|
||||
return Math.Max(8000, sourceSampleRate);
|
||||
}
|
||||
|
||||
protected static int QualityToBitRate(ConversionQuality quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case ConversionQuality.Low:
|
||||
return 96000;
|
||||
case ConversionQuality.Medium:
|
||||
return 128000;
|
||||
}
|
||||
|
||||
return 192000;
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// 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.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Target formats supported for audio source conversions.
|
||||
/// </summary>
|
||||
public enum ConversionFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Microsoft ADPCM encoding technique using 4 bits
|
||||
/// </summary>
|
||||
Adpcm,
|
||||
|
||||
/// <summary>
|
||||
/// 8/16-bit mono/stereo PCM audio 8KHz-48KHz
|
||||
/// </summary>
|
||||
Pcm,
|
||||
|
||||
/// <summary>
|
||||
/// Windows Media CBR formats (64 kbps, 128 kbps, 192 kbps)
|
||||
/// </summary>
|
||||
WindowsMedia,
|
||||
|
||||
/// <summary>
|
||||
/// The Xbox compression format
|
||||
/// </summary>
|
||||
Xma,
|
||||
|
||||
/// <summary>
|
||||
/// QuickTime ADPCM format
|
||||
/// </summary>
|
||||
ImaAdpcm,
|
||||
|
||||
/// <summary>
|
||||
/// Advanced Audio Coding
|
||||
/// </summary>
|
||||
Aac,
|
||||
|
||||
/// <summary>
|
||||
/// Vorbis open, patent-free audio encoding
|
||||
/// </summary>
|
||||
Vorbis,
|
||||
}
|
||||
}
|
||||
+27
@@ -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.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Compression quality of the audio content.
|
||||
/// </summary>
|
||||
public enum ConversionQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// High compression yielding lower file size, but could compromise audio quality
|
||||
/// </summary>
|
||||
Low,
|
||||
|
||||
/// <summary>
|
||||
/// Moderate compression resulting in a compromise between audio quality and file size
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// Lowest compression, but the best audio quality
|
||||
/// </summary>
|
||||
Best,
|
||||
}
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Audio
|
||||
{
|
||||
internal class DefaultAudioProfile : AudioProfile
|
||||
{
|
||||
public override bool Supports(TargetPlatform platform)
|
||||
{
|
||||
return platform == TargetPlatform.Android ||
|
||||
platform == TargetPlatform.DesktopGL ||
|
||||
platform == TargetPlatform.MacOSX ||
|
||||
platform == TargetPlatform.NativeClient ||
|
||||
platform == TargetPlatform.RaspberryPi ||
|
||||
platform == TargetPlatform.Windows ||
|
||||
platform == TargetPlatform.WindowsPhone8 ||
|
||||
platform == TargetPlatform.WindowsStoreApp ||
|
||||
platform == TargetPlatform.iOS;
|
||||
}
|
||||
|
||||
public override ConversionQuality ConvertAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content)
|
||||
{
|
||||
// Default to PCM data, or ADPCM if the source is ADPCM.
|
||||
var targetFormat = ConversionFormat.Pcm;
|
||||
if (quality != ConversionQuality.Best || content.Format.Format == 2 || content.Format.Format == 17)
|
||||
{
|
||||
if (platform == TargetPlatform.iOS || platform == TargetPlatform.MacOSX || platform == TargetPlatform.DesktopGL)
|
||||
targetFormat = ConversionFormat.ImaAdpcm;
|
||||
else
|
||||
targetFormat = ConversionFormat.Adpcm;
|
||||
}
|
||||
|
||||
return ConvertToFormat(content, targetFormat, quality, null);
|
||||
}
|
||||
|
||||
public override ConversionQuality ConvertStreamingAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content, ref string outputFileName)
|
||||
{
|
||||
// Most platforms will use AAC ("mp4") by default
|
||||
var targetFormat = ConversionFormat.Aac;
|
||||
|
||||
if ( platform == TargetPlatform.Windows ||
|
||||
platform == TargetPlatform.WindowsPhone8 ||
|
||||
platform == TargetPlatform.WindowsStoreApp)
|
||||
targetFormat = ConversionFormat.WindowsMedia;
|
||||
|
||||
else if (platform == TargetPlatform.DesktopGL)
|
||||
targetFormat = ConversionFormat.Vorbis;
|
||||
|
||||
// Get the song output path with the target format extension.
|
||||
outputFileName = Path.ChangeExtension(outputFileName, AudioHelper.GetExtension(targetFormat));
|
||||
|
||||
// Make sure the output folder for the file exists.
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputFileName));
|
||||
|
||||
return ConvertToFormat(content, targetFormat, quality, outputFileName);
|
||||
}
|
||||
|
||||
public static void ProbeFormat(string sourceFile, out AudioFileType audioFileType, out AudioFormat audioFormat, out TimeSpan duration, out int loopStart, out int loopLength)
|
||||
{
|
||||
string ffprobeStdout, ffprobeStderr;
|
||||
var ffprobeExitCode = ExternalTool.Run(
|
||||
"ffprobe",
|
||||
string.Format("-i \"{0}\" -show_format -show_entries streams -v quiet -of flat", sourceFile),
|
||||
out ffprobeStdout,
|
||||
out ffprobeStderr);
|
||||
if (ffprobeExitCode != 0)
|
||||
throw new InvalidOperationException("ffprobe exited with non-zero exit code.");
|
||||
|
||||
// Set default values if information is not available.
|
||||
int averageBytesPerSecond = 0;
|
||||
int bitsPerSample = 0;
|
||||
int blockAlign = 0;
|
||||
int channelCount = 0;
|
||||
int sampleRate = 0;
|
||||
int format = 0;
|
||||
string sampleFormat = null;
|
||||
double durationInSeconds = 0;
|
||||
var formatName = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var numberFormat = CultureInfo.InvariantCulture.NumberFormat;
|
||||
foreach (var line in ffprobeStdout.Split(new[] {'\r', '\n', '\0'}, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var kv = line.Split(new[] {'='}, 2);
|
||||
|
||||
switch (kv[0])
|
||||
{
|
||||
case "streams.stream.0.sample_rate":
|
||||
sampleRate = int.Parse(kv[1].Trim('"'), numberFormat);
|
||||
break;
|
||||
case "streams.stream.0.bits_per_sample":
|
||||
bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat);
|
||||
break;
|
||||
case "streams.stream.0.start_time":
|
||||
{
|
||||
double seconds;
|
||||
if (double.TryParse(kv[1].Trim('"'), NumberStyles.Any, numberFormat, out seconds))
|
||||
durationInSeconds += seconds;
|
||||
break;
|
||||
}
|
||||
case "streams.stream.0.duration":
|
||||
durationInSeconds += double.Parse(kv[1].Trim('"'), numberFormat);
|
||||
break;
|
||||
case "streams.stream.0.channels":
|
||||
channelCount = int.Parse(kv[1].Trim('"'), numberFormat);
|
||||
break;
|
||||
case "streams.stream.0.sample_fmt":
|
||||
sampleFormat = kv[1].Trim('"').ToLowerInvariant();
|
||||
break;
|
||||
case "streams.stream.0.bit_rate":
|
||||
averageBytesPerSecond = (int.Parse(kv[1].Trim('"'), numberFormat)/8);
|
||||
break;
|
||||
case "format.format_name":
|
||||
formatName = kv[1].Trim('"').ToLowerInvariant();
|
||||
break;
|
||||
case "streams.stream.0.codec_tag":
|
||||
{
|
||||
var hex = kv[1].Substring(3, kv[1].Length - 4);
|
||||
format = int.Parse(hex, NumberStyles.HexNumber);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to parse ffprobe output.", ex);
|
||||
}
|
||||
|
||||
// XNA seems to use the sample format for the bits per sample
|
||||
// in the case of non-PCM formats like MP3 and WMA.
|
||||
if (bitsPerSample == 0 && sampleFormat != null)
|
||||
{
|
||||
switch (sampleFormat)
|
||||
{
|
||||
case "u8":
|
||||
case "u8p":
|
||||
bitsPerSample = 8;
|
||||
break;
|
||||
case "s16":
|
||||
case "s16p":
|
||||
bitsPerSample = 16;
|
||||
break;
|
||||
case "s32":
|
||||
case "s32p":
|
||||
case "flt":
|
||||
case "fltp":
|
||||
bitsPerSample = 32;
|
||||
break;
|
||||
case "dbl":
|
||||
case "dblp":
|
||||
bitsPerSample = 64;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out the file type.
|
||||
var durationMs = (int)Math.Floor(durationInSeconds * 1000.0);
|
||||
if (formatName == "wav")
|
||||
{
|
||||
audioFileType = AudioFileType.Wav;
|
||||
}
|
||||
else if (formatName == "mp3")
|
||||
{
|
||||
audioFileType = AudioFileType.Mp3;
|
||||
format = 1;
|
||||
durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0);
|
||||
bitsPerSample = Math.Min(bitsPerSample, 16);
|
||||
}
|
||||
else if (formatName == "wma" || formatName == "asf")
|
||||
{
|
||||
audioFileType = AudioFileType.Wma;
|
||||
format = 1;
|
||||
durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0);
|
||||
bitsPerSample = Math.Min(bitsPerSample, 16);
|
||||
}
|
||||
else if (formatName == "ogg")
|
||||
{
|
||||
audioFileType = AudioFileType.Ogg;
|
||||
format = 1;
|
||||
durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0);
|
||||
bitsPerSample = Math.Min(bitsPerSample, 16);
|
||||
}
|
||||
else
|
||||
audioFileType = (AudioFileType) (-1);
|
||||
|
||||
// XNA seems to calculate the block alignment directly from
|
||||
// the bits per sample and channel count regardless of the
|
||||
// format of the audio data.
|
||||
// ffprobe doesn't report blockAlign for ADPCM and we cannot calculate it like this
|
||||
if (bitsPerSample > 0 && (format != 2 && format != 17))
|
||||
blockAlign = (bitsPerSample * channelCount) / 8;
|
||||
|
||||
// XNA seems to only be accurate to the millisecond.
|
||||
duration = TimeSpan.FromMilliseconds(durationMs);
|
||||
|
||||
// Looks like XNA calculates the average bps from
|
||||
// the sample rate and block alignment.
|
||||
if (blockAlign > 0)
|
||||
averageBytesPerSecond = sampleRate * blockAlign;
|
||||
|
||||
audioFormat = new AudioFormat(
|
||||
averageBytesPerSecond,
|
||||
bitsPerSample,
|
||||
blockAlign,
|
||||
channelCount,
|
||||
format,
|
||||
sampleRate);
|
||||
|
||||
// Loop start and length in number of samples. For some
|
||||
// reason XNA doesn't report loop length for non-WAV sources.
|
||||
loopStart = 0;
|
||||
if (audioFileType != AudioFileType.Wav)
|
||||
loopLength = 0;
|
||||
else
|
||||
loopLength = (int)Math.Floor(sampleRate * durationInSeconds);
|
||||
}
|
||||
|
||||
internal static byte[] StripRiffWaveHeader(byte[] data, out AudioFormat audioFormat)
|
||||
{
|
||||
audioFormat = null;
|
||||
|
||||
using (var reader = new BinaryReader(new MemoryStream(data)))
|
||||
{
|
||||
var signature = new string(reader.ReadChars(4));
|
||||
if (signature != "RIFF")
|
||||
return data;
|
||||
|
||||
reader.ReadInt32(); // riff_chunck_size
|
||||
|
||||
var wformat = new string(reader.ReadChars(4));
|
||||
if (wformat != "WAVE")
|
||||
return data;
|
||||
|
||||
// Look for the data chunk.
|
||||
while (true)
|
||||
{
|
||||
var chunkSignature = new string(reader.ReadChars(4));
|
||||
if (chunkSignature.ToLowerInvariant() == "data")
|
||||
break;
|
||||
if (chunkSignature.ToLowerInvariant() == "fmt ")
|
||||
{
|
||||
int fmtLength = reader.ReadInt32();
|
||||
short formatTag = reader.ReadInt16();
|
||||
short channels = reader.ReadInt16();
|
||||
int sampleRate = reader.ReadInt32();
|
||||
int avgBytesPerSec = reader.ReadInt32();
|
||||
short blockAlign = reader.ReadInt16();
|
||||
short bitsPerSample = reader.ReadInt16();
|
||||
audioFormat = new AudioFormat(avgBytesPerSec, bitsPerSample, blockAlign, channels, formatTag, sampleRate);
|
||||
|
||||
fmtLength -= 2 + 2 + 4 + 4 + 2 + 2;
|
||||
if (fmtLength < 0)
|
||||
throw new InvalidOperationException("riff wave header has unexpected format");
|
||||
reader.BaseStream.Seek(fmtLength, SeekOrigin.Current);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.BaseStream.Seek(reader.ReadInt32(), SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
var dataSize = reader.ReadInt32();
|
||||
data = reader.ReadBytes(dataSize);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void WritePcmFile(AudioContent content, string saveToFile, int bitRate = 192000, int? sampeRate = null)
|
||||
{
|
||||
string ffmpegStdout, ffmpegStderr;
|
||||
var ffmpegExitCode = ExternalTool.Run(
|
||||
"ffmpeg",
|
||||
string.Format(
|
||||
"-y -i \"{0}\" -vn -c:a pcm_s16le -b:a {2} {3} -f:a wav -strict experimental \"{1}\"",
|
||||
content.FileName,
|
||||
saveToFile,
|
||||
bitRate,
|
||||
sampeRate != null ? "-ar " + sampeRate.Value : ""
|
||||
),
|
||||
out ffmpegStdout,
|
||||
out ffmpegStderr);
|
||||
if (ffmpegExitCode != 0)
|
||||
throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
|
||||
}
|
||||
|
||||
public static ConversionQuality ConvertToFormat(AudioContent content, ConversionFormat formatType, ConversionQuality quality, string saveToFile)
|
||||
{
|
||||
var temporaryOutput = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
string ffmpegCodecName, ffmpegMuxerName;
|
||||
//int format;
|
||||
switch (formatType)
|
||||
{
|
||||
case ConversionFormat.Adpcm:
|
||||
// ADPCM Microsoft
|
||||
ffmpegCodecName = "adpcm_ms";
|
||||
ffmpegMuxerName = "wav";
|
||||
//format = 0x0002; /* WAVE_FORMAT_ADPCM */
|
||||
break;
|
||||
case ConversionFormat.Pcm:
|
||||
// XNA seems to preserve the bit size of the input
|
||||
// format when converting to PCM.
|
||||
if (content.Format.BitsPerSample == 8)
|
||||
ffmpegCodecName = "pcm_u8";
|
||||
else if (content.Format.BitsPerSample == 32 && content.Format.Format == 3)
|
||||
ffmpegCodecName = "pcm_f32le";
|
||||
else
|
||||
ffmpegCodecName = "pcm_s16le";
|
||||
ffmpegMuxerName = "wav";
|
||||
//format = 0x0001; /* WAVE_FORMAT_PCM */
|
||||
break;
|
||||
case ConversionFormat.WindowsMedia:
|
||||
// Windows Media Audio 2
|
||||
ffmpegCodecName = "wmav2";
|
||||
ffmpegMuxerName = "asf";
|
||||
//format = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */
|
||||
break;
|
||||
case ConversionFormat.Xma:
|
||||
throw new NotSupportedException(
|
||||
"XMA is not a supported encoding format. It is specific to the Xbox 360.");
|
||||
case ConversionFormat.ImaAdpcm:
|
||||
// ADPCM IMA WAV
|
||||
ffmpegCodecName = "adpcm_ima_wav";
|
||||
ffmpegMuxerName = "wav";
|
||||
//format = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */
|
||||
break;
|
||||
case ConversionFormat.Aac:
|
||||
// AAC (Advanced Audio Coding)
|
||||
// Requires -strict experimental
|
||||
ffmpegCodecName = "aac";
|
||||
ffmpegMuxerName = "ipod";
|
||||
//format = 0x0000; /* WAVE_FORMAT_UNKNOWN */
|
||||
break;
|
||||
case ConversionFormat.Vorbis:
|
||||
// Vorbis
|
||||
ffmpegCodecName = "libvorbis";
|
||||
ffmpegMuxerName = "ogg";
|
||||
//format = 0x0000; /* WAVE_FORMAT_UNKNOWN */
|
||||
break;
|
||||
default:
|
||||
// Unknown format
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
string ffmpegStdout, ffmpegStderr;
|
||||
int ffmpegExitCode;
|
||||
do
|
||||
{
|
||||
ffmpegExitCode = ExternalTool.Run(
|
||||
"ffmpeg",
|
||||
string.Format(
|
||||
"-y -i \"{0}\" -vn -c:a {1} -b:a {2} -ar {3} -f:a {4} -strict experimental \"{5}\"",
|
||||
content.FileName,
|
||||
ffmpegCodecName,
|
||||
QualityToBitRate(quality),
|
||||
QualityToSampleRate(quality, content.Format.SampleRate),
|
||||
ffmpegMuxerName,
|
||||
temporaryOutput),
|
||||
out ffmpegStdout,
|
||||
out ffmpegStderr);
|
||||
if (ffmpegExitCode != 0)
|
||||
quality--;
|
||||
} while (quality >= 0 && ffmpegExitCode != 0);
|
||||
|
||||
if (ffmpegExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
|
||||
}
|
||||
|
||||
byte[] rawData;
|
||||
using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
rawData = new byte[fs.Length];
|
||||
fs.Read(rawData, 0, rawData.Length);
|
||||
}
|
||||
|
||||
if (saveToFile != null)
|
||||
{
|
||||
using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write))
|
||||
fs.Write(rawData, 0, rawData.Length);
|
||||
}
|
||||
|
||||
// Use probe to get the final format and information on the converted file.
|
||||
AudioFileType audioFileType;
|
||||
AudioFormat audioFormat;
|
||||
TimeSpan duration;
|
||||
int loopStart, loopLength;
|
||||
ProbeFormat(temporaryOutput, out audioFileType, out audioFormat, out duration, out loopStart, out loopLength);
|
||||
|
||||
AudioFormat riffAudioFormat;
|
||||
byte[] data = StripRiffWaveHeader(rawData, out riffAudioFormat);
|
||||
|
||||
// deal with adpcm
|
||||
if (audioFormat.Format == 2 || audioFormat.Format == 17)
|
||||
{
|
||||
// riff contains correct blockAlign
|
||||
audioFormat = riffAudioFormat;
|
||||
|
||||
// fix loopLength -> has to be multiple of sample per block
|
||||
// see https://msdn.microsoft.com/de-de/library/windows/desktop/ee415711(v=vs.85).aspx
|
||||
int samplesPerBlock = SampleAlignment(audioFormat);
|
||||
loopLength = (int)(audioFormat.SampleRate * duration.TotalSeconds);
|
||||
int remainder = loopLength % samplesPerBlock;
|
||||
loopLength += samplesPerBlock - remainder;
|
||||
}
|
||||
|
||||
content.SetData(data, audioFormat, duration, loopStart, loopLength);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExternalTool.DeleteFile(temporaryOutput);
|
||||
}
|
||||
|
||||
return quality;
|
||||
}
|
||||
|
||||
// Converts block alignment in bytes to sample alignment, primarily for compressed formats
|
||||
// Calculation of sample alignment from http://kcat.strangesoft.net/openal-extensions/SOFT_block_alignment.txt
|
||||
static int SampleAlignment(AudioFormat format)
|
||||
{
|
||||
switch (format.Format)
|
||||
{
|
||||
case 2: // MS-ADPCM
|
||||
return (format.BlockAlign / format.ChannelCount - 7) * 2 + 2;
|
||||
case 17: // IMA/ADPCM
|
||||
return (format.BlockAlign / format.ChannelCount - 4) / 4 * 8 + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Builder.Convertors
|
||||
{
|
||||
public class StringToColorConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
return true;
|
||||
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
if (destinationType != typeof (string))
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
|
||||
var color = (Color)value;
|
||||
return string.Format("{0},{1},{2},{3}", color.R, color.G, color.B, color.A);
|
||||
}
|
||||
|
||||
public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof (string))
|
||||
return true;
|
||||
|
||||
return base.CanConvertFrom (context, sourceType);
|
||||
}
|
||||
|
||||
public override object ConvertFrom (ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
|
||||
{
|
||||
if (value.GetType () == typeof (string)) {
|
||||
string[] values = ((string)value).Split(new char[] {','},StringSplitOptions.None);
|
||||
if (values.Length == 4)
|
||||
{
|
||||
var r = int.Parse(values[0].Trim());
|
||||
var g = int.Parse(values[1].Trim());
|
||||
var b = int.Parse(values[2].Trim());
|
||||
var a = int.Parse(values[3].Trim());
|
||||
return new Microsoft.Xna.Framework.Color(r, g, b, a);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(string.Format("Could not convert from string({0}) to Color, expected format is 'r,g,b,a'", value));
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertFrom (context, culture, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// 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.IO;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public static class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks deletes a file from disk without throwing exceptions.
|
||||
/// </summary>
|
||||
public static void DeleteIfExists(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public static class PathHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// The/universal/standard/directory/seperator.
|
||||
/// </summary>
|
||||
public const char DirectorySeparator = '/';
|
||||
|
||||
/// <summary>
|
||||
/// Returns a path string normalized to the/universal/standard.
|
||||
/// </summary>
|
||||
public static string Normalize(string path)
|
||||
{
|
||||
return path.Replace('\\', '/');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a directory path string normalized to the/universal/standard
|
||||
/// with a trailing seperator.
|
||||
/// </summary>
|
||||
public static string NormalizeDirectory(string path)
|
||||
{
|
||||
return path.Replace('\\', '/').TrimEnd('/') + '/';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a path string normalized to the\Windows\standard.
|
||||
/// </summary>
|
||||
public static string NormalizeWindows(string path)
|
||||
{
|
||||
return path.Replace('/', '\\');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a path relative to the base path.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The path to make relative to. Must end with directory seperator.</param>
|
||||
/// <param name="path">The path to be made relative to the basePath.</param>
|
||||
/// <returns>The relative path or the original string if it is not absolute or cannot be made relative.</returns>
|
||||
public static string GetRelativePath(string basePath, string path)
|
||||
{
|
||||
Uri uri;
|
||||
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
|
||||
return path;
|
||||
|
||||
uri = new Uri(basePath).MakeRelativeUri(uri);
|
||||
var str = Uri.UnescapeDataString(uri.ToString());
|
||||
|
||||
return Normalize(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public class PipelineBuildEvent
|
||||
{
|
||||
private static readonly OpaqueDataDictionary EmptyParameters = new OpaqueDataDictionary();
|
||||
public static readonly string Extension = ".mgcontent";
|
||||
|
||||
public PipelineBuildEvent()
|
||||
{
|
||||
SourceFile = string.Empty;
|
||||
DestFile = string.Empty;
|
||||
Importer = string.Empty;
|
||||
Processor = string.Empty;
|
||||
Parameters = new OpaqueDataDictionary();
|
||||
ParametersXml = new List<Pair>();
|
||||
Dependencies = new List<string>();
|
||||
BuildAsset = new List<string>();
|
||||
BuildOutput = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path to the source file.
|
||||
/// </summary>
|
||||
public string SourceFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date/time stamp of the source file.
|
||||
/// </summary>
|
||||
public DateTime SourceTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path to the output file.
|
||||
/// </summary>
|
||||
public string DestFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date/time stamp of the destination file.
|
||||
/// </summary>
|
||||
public DateTime DestTime { get; set; }
|
||||
|
||||
public string Importer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date/time stamp of the DLL containing the importer.
|
||||
/// </summary>
|
||||
public DateTime ImporterTime { get; set; }
|
||||
|
||||
public string Processor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date/time stamp of the DLL containing the processor.
|
||||
/// </summary>
|
||||
public DateTime ProcessorTime { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public OpaqueDataDictionary Parameters { get; set; }
|
||||
|
||||
public class Pair
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
[XmlElement("Parameters")]
|
||||
public List<Pair> ParametersXml { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dependencies.
|
||||
/// </summary>
|
||||
/// <value>The dependencies.</value>
|
||||
/// <remarks>
|
||||
/// Dependencies are extra files that are required in addition to the <see cref="SourceFile"/>.
|
||||
/// Dependencies are added using <see cref="ContentProcessorContext.AddDependency"/>. Changes
|
||||
/// to the dependent file causes a rebuilt of the content.
|
||||
/// </remarks>
|
||||
public List<string> Dependencies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the additional (nested) assets.
|
||||
/// </summary>
|
||||
/// <value>The additional (nested) assets.</value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Additional assets are built by using an <see cref="ExternalReference{T}"/> and calling
|
||||
/// <see cref="ContentProcessorContext.BuildAndLoadAsset{TInput,TOutput}(ExternalReference{TInput},string)"/>
|
||||
/// or <see cref="ContentProcessorContext.BuildAsset{TInput,TOutput}(ExternalReference{TInput},string)"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Examples: The mesh processor may build textures and effects in addition to the mesh.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public List<string> BuildAsset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the related output files.
|
||||
/// </summary>
|
||||
/// <value>The related output files.</value>
|
||||
/// <remarks>
|
||||
/// Related output files are non-XNB files that are included in addition to the XNB files.
|
||||
/// Related output files need to be copied to the output folder by a content processor and
|
||||
/// registered by calling <see cref="ContentProcessorContext.AddOutputFile"/>.
|
||||
/// </remarks>
|
||||
public List<string> BuildOutput { get; set; }
|
||||
|
||||
public static PipelineBuildEvent Load(string filePath)
|
||||
{
|
||||
var fullFilePath = Path.GetFullPath(filePath);
|
||||
var deserializer = new XmlSerializer(typeof (PipelineBuildEvent));
|
||||
PipelineBuildEvent pipelineEvent;
|
||||
try
|
||||
{
|
||||
using (var textReader = new StreamReader(fullFilePath))
|
||||
pipelineEvent = (PipelineBuildEvent) deserializer.Deserialize(textReader);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Repopulate the parameters from the serialized state.
|
||||
foreach (var pair in pipelineEvent.ParametersXml)
|
||||
pipelineEvent.Parameters.Add(pair.Key, pair.Value);
|
||||
pipelineEvent.ParametersXml.Clear();
|
||||
|
||||
return pipelineEvent;
|
||||
}
|
||||
|
||||
public void Save(string filePath)
|
||||
{
|
||||
var fullFilePath = Path.GetFullPath(filePath);
|
||||
// Make sure the directory exists.
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullFilePath) + Path.DirectorySeparatorChar);
|
||||
|
||||
// Convert the parameters into something we can serialize.
|
||||
ParametersXml.Clear();
|
||||
foreach (var pair in Parameters)
|
||||
ParametersXml.Add(new Pair { Key = pair.Key, Value = ConvertToString(pair.Value) });
|
||||
|
||||
// Serialize our state.
|
||||
var serializer = new XmlSerializer(typeof (PipelineBuildEvent));
|
||||
using (var textWriter = new StreamWriter(fullFilePath, false, new UTF8Encoding(false)))
|
||||
serializer.Serialize(textWriter, this);
|
||||
}
|
||||
|
||||
public bool NeedsRebuild(PipelineManager manager, PipelineBuildEvent cachedEvent)
|
||||
{
|
||||
// If we have no previously cached build event then we cannot
|
||||
// be sure that the state hasn't changed... force a rebuild.
|
||||
if (cachedEvent == null)
|
||||
return true;
|
||||
|
||||
// Verify that the last write time of the source file matches
|
||||
// what we recorded when it was built. If it is different
|
||||
// that means someone modified it and we need to rebuild.
|
||||
var sourceWriteTime = File.GetLastWriteTime(SourceFile);
|
||||
if (cachedEvent.SourceTime != sourceWriteTime)
|
||||
return true;
|
||||
|
||||
// Do the same test for the dest file.
|
||||
var destWriteTime = File.GetLastWriteTime(DestFile);
|
||||
if (cachedEvent.DestTime != destWriteTime)
|
||||
return true;
|
||||
|
||||
// If the source file is newer than the dest file
|
||||
// then it must have been updated and needs a rebuild.
|
||||
if (sourceWriteTime >= destWriteTime)
|
||||
return true;
|
||||
|
||||
// Are any of the dependancy files newer than the dest file?
|
||||
foreach (var depFile in cachedEvent.Dependencies)
|
||||
{
|
||||
if (File.GetLastWriteTime(depFile) >= destWriteTime)
|
||||
return true;
|
||||
}
|
||||
|
||||
// This shouldn't happen... but if the source or dest files changed
|
||||
// then force a rebuild.
|
||||
if (cachedEvent.SourceFile != SourceFile ||
|
||||
cachedEvent.DestFile != DestFile)
|
||||
return true;
|
||||
|
||||
// Did the importer assembly change?
|
||||
if (manager.GetImporterAssemblyTimestamp(cachedEvent.Importer) > cachedEvent.ImporterTime)
|
||||
return true;
|
||||
|
||||
// Did the importer change?
|
||||
if (cachedEvent.Importer != Importer)
|
||||
return true;
|
||||
|
||||
// Did the processor assembly change?
|
||||
if (manager.GetProcessorAssemblyTimestamp(cachedEvent.Processor) > cachedEvent.ProcessorTime)
|
||||
return true;
|
||||
|
||||
// Did the processor change?
|
||||
if (cachedEvent.Processor != Processor)
|
||||
return true;
|
||||
|
||||
// Did the parameters change?
|
||||
var defaultValues = manager.GetProcessorDefaultValues(Processor);
|
||||
if (!AreParametersEqual(cachedEvent.Parameters, Parameters, defaultValues))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool AreParametersEqual(OpaqueDataDictionary parameters0, OpaqueDataDictionary parameters1, OpaqueDataDictionary defaultValues)
|
||||
{
|
||||
Debug.Assert(defaultValues != null, "defaultValues must not be empty.");
|
||||
Debug.Assert(EmptyParameters != null && EmptyParameters.Count == 0);
|
||||
|
||||
// Same reference or both null?
|
||||
if (parameters0 == parameters1)
|
||||
return true;
|
||||
|
||||
if (parameters0 == null)
|
||||
parameters0 = EmptyParameters;
|
||||
if (parameters1 == null)
|
||||
parameters1 = EmptyParameters;
|
||||
|
||||
// Are both dictionaries empty?
|
||||
if (parameters0.Count == 0 && parameters1.Count == 0)
|
||||
return true;
|
||||
|
||||
// Compare the values with the second dictionary or
|
||||
// the default values.
|
||||
if (parameters0.Count < parameters1.Count)
|
||||
{
|
||||
var dummy = parameters0;
|
||||
parameters0 = parameters1;
|
||||
parameters1 = dummy;
|
||||
}
|
||||
|
||||
// Compare parameters0 with parameters1 or defaultValues.
|
||||
foreach (var pair in parameters0)
|
||||
{
|
||||
object value0 = pair.Value;
|
||||
object value1;
|
||||
|
||||
// Search for matching parameter.
|
||||
if (!parameters1.TryGetValue(pair.Key, out value1) && !defaultValues.TryGetValue(pair.Key, out value1))
|
||||
return false;
|
||||
|
||||
if (!AreEqual(value0, value1))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare parameters which are only in parameters1 with defaultValues.
|
||||
foreach (var pair in parameters1)
|
||||
{
|
||||
if (parameters0.ContainsKey(pair.Key))
|
||||
continue;
|
||||
|
||||
object defaultValue;
|
||||
if (!defaultValues.TryGetValue(pair.Key, out defaultValue))
|
||||
return false;
|
||||
|
||||
if (!AreEqual(pair.Value, defaultValue))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AreEqual(object value0, object value1)
|
||||
{
|
||||
// Are values equal or both null?
|
||||
if (Equals(value0, value1))
|
||||
return true;
|
||||
|
||||
// Is one value null?
|
||||
if (value0 == null || value1 == null)
|
||||
return false;
|
||||
|
||||
// Values are of different type: Compare string representation.
|
||||
if (ConvertToString(value0) != ConvertToString(value1))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ConvertToString(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
var typeConverter = TypeDescriptor.GetConverter(value.GetType());
|
||||
return typeConverter.ConvertToInvariantString(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
+32
@@ -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 System;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public class PipelineBuildLogger : ContentBuildLogger
|
||||
{
|
||||
public override void LogMessage(string message, params object[] messageArgs)
|
||||
{
|
||||
System.Diagnostics.Trace.WriteLine(string.Format(message, messageArgs));
|
||||
}
|
||||
|
||||
public override void LogImportantMessage(string message, params object[] messageArgs)
|
||||
{
|
||||
// TODO: How do i make it high importance?
|
||||
System.Diagnostics.Trace.WriteLine(string.Format(message, messageArgs));
|
||||
}
|
||||
|
||||
public override void LogWarning(string helpLink, ContentIdentity contentIdentity, string message, params object[] messageArgs)
|
||||
{
|
||||
var msg = string.Format(message, messageArgs);
|
||||
var fileName = GetCurrentFilename(contentIdentity);
|
||||
System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", fileName, msg));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public class PipelineImporterContext : ContentImporterContext
|
||||
{
|
||||
private readonly PipelineManager _manager;
|
||||
|
||||
public PipelineImporterContext(PipelineManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public override string IntermediateDirectory { get { return _manager.IntermediateDirectory; } }
|
||||
public override string OutputDirectory { get { return _manager.OutputDirectory; } }
|
||||
public override ContentBuildLogger Logger { get { return _manager.Logger; } }
|
||||
|
||||
public override void AddDependency(string filename)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+936
@@ -0,0 +1,936 @@
|
||||
// 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 System.Reflection;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Builder.Convertors;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public class PipelineManager
|
||||
{
|
||||
[DebuggerDisplay("ImporterInfo: {type.Name}")]
|
||||
private struct ImporterInfo
|
||||
{
|
||||
public ContentImporterAttribute attribute;
|
||||
public Type type;
|
||||
public DateTime assemblyTimestamp;
|
||||
};
|
||||
|
||||
private List<ImporterInfo> _importers;
|
||||
|
||||
[DebuggerDisplay("ProcessorInfo: {type.Name}")]
|
||||
private struct ProcessorInfo
|
||||
{
|
||||
public ContentProcessorAttribute attribute;
|
||||
public Type type;
|
||||
public DateTime assemblyTimestamp;
|
||||
};
|
||||
|
||||
private List<ProcessorInfo> _processors;
|
||||
|
||||
private List<Type> _writers;
|
||||
|
||||
// Keep track of all built assets. (Required to resolve automatic names "AssetName_n".)
|
||||
// Key = absolute, normalized path of source file
|
||||
// Value = list of build events
|
||||
// (Note: When using external references, an asset may be built multiple times
|
||||
// with different parameters.)
|
||||
private readonly Dictionary<string, List<PipelineBuildEvent>> _pipelineBuildEvents;
|
||||
|
||||
// Store default values for content processor parameters. (Necessary to compare processor
|
||||
// parameters. See PipelineBuildEvent.AreParametersEqual.)
|
||||
// Key = name of content processor
|
||||
// Value = processor parameters
|
||||
private readonly Dictionary<string, OpaqueDataDictionary> _processorDefaultValues;
|
||||
|
||||
public string ProjectDirectory { get; private set; }
|
||||
public string OutputDirectory { get; private set; }
|
||||
public string IntermediateDirectory { get; private set; }
|
||||
|
||||
public ContentStatsCollection ContentStats { get; private set; }
|
||||
|
||||
private ContentCompiler _compiler;
|
||||
|
||||
public ContentBuildLogger Logger { get; set; }
|
||||
|
||||
public List<string> Assemblies { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current target graphics profile for which all content is built.
|
||||
/// </summary>
|
||||
public GraphicsProfile Profile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current target platform for which all content is built.
|
||||
/// </summary>
|
||||
public TargetPlatform Platform { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The build configuration passed thru to content processors.
|
||||
/// </summary>
|
||||
public string Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the content is compressed.
|
||||
/// </summary>
|
||||
public bool CompressContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true exceptions thrown from within an importer or processor are caught and then
|
||||
/// thrown from the context. Default value is true.
|
||||
/// </summary>
|
||||
public bool RethrowExceptions { get; set; }
|
||||
|
||||
public PipelineManager(string projectDir, string outputDir, string intermediateDir)
|
||||
{
|
||||
_pipelineBuildEvents = new Dictionary<string, List<PipelineBuildEvent>>();
|
||||
_processorDefaultValues = new Dictionary<string, OpaqueDataDictionary>();
|
||||
RethrowExceptions = true;
|
||||
|
||||
Assemblies = new List<string>();
|
||||
Assemblies.Add(null);
|
||||
Logger = new PipelineBuildLogger();
|
||||
|
||||
ProjectDirectory = PathHelper.NormalizeDirectory(projectDir);
|
||||
OutputDirectory = PathHelper.NormalizeDirectory(outputDir);
|
||||
IntermediateDirectory = PathHelper.NormalizeDirectory(intermediateDir);
|
||||
|
||||
RegisterCustomConverters();
|
||||
|
||||
// Load the previous content stats.
|
||||
ContentStats = new ContentStatsCollection();
|
||||
ContentStats.PreviousStats = ContentStatsCollection.Read(intermediateDir);
|
||||
}
|
||||
|
||||
public void AssignTypeConverter<TType, TTypeConverter> ()
|
||||
{
|
||||
TypeDescriptor.AddAttributes (typeof (TType), new TypeConverterAttribute (typeof (TTypeConverter)));
|
||||
}
|
||||
|
||||
private void RegisterCustomConverters ()
|
||||
{
|
||||
AssignTypeConverter<Microsoft.Xna.Framework.Color, StringToColorConverter> ();
|
||||
}
|
||||
|
||||
public void AddAssembly(string assemblyFilePath)
|
||||
{
|
||||
if (assemblyFilePath == null)
|
||||
throw new ArgumentException("assemblyFilePath cannot be null!");
|
||||
if (!Path.IsPathRooted(assemblyFilePath))
|
||||
throw new ArgumentException("assemblyFilePath must be absolute!");
|
||||
|
||||
// Make sure we're not adding the same assembly twice.
|
||||
assemblyFilePath = PathHelper.Normalize(assemblyFilePath);
|
||||
if (!Assemblies.Contains(assemblyFilePath))
|
||||
{
|
||||
Assemblies.Add(assemblyFilePath);
|
||||
|
||||
//TODO need better way to update caches
|
||||
_processors = null;
|
||||
_importers = null;
|
||||
_writers = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveAssemblies()
|
||||
{
|
||||
_importers = new List<ImporterInfo>();
|
||||
_processors = new List<ProcessorInfo>();
|
||||
_writers = new List<Type>();
|
||||
|
||||
// Finally load the pipeline assemblies.
|
||||
foreach (var assemblyPath in Assemblies)
|
||||
{
|
||||
Type[] exportedTypes;
|
||||
DateTime assemblyTimestamp;
|
||||
try
|
||||
{
|
||||
Assembly a;
|
||||
if (string.IsNullOrEmpty(assemblyPath))
|
||||
a = Assembly.GetExecutingAssembly();
|
||||
else
|
||||
a = Assembly.LoadFrom(assemblyPath);
|
||||
|
||||
exportedTypes = a.GetTypes();
|
||||
assemblyTimestamp = File.GetLastWriteTime(a.Location);
|
||||
}
|
||||
catch (BadImageFormatException e)
|
||||
{
|
||||
Logger.LogWarning(null, null, "Assembly is either corrupt or built using a different " +
|
||||
"target platform than this process. Reference another target architecture (x86, x64, " +
|
||||
"AnyCPU, etc.) of this assembly. '{0}': {1}", assemblyPath, e.Message);
|
||||
// The assembly failed to load... nothing
|
||||
// we can do but ignore it.
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning(null, null, "Failed to load assembly '{0}': {1}", assemblyPath, e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var t in exportedTypes)
|
||||
{
|
||||
if (t.IsAbstract)
|
||||
continue;
|
||||
|
||||
if (t.GetInterface(@"IContentImporter") != null)
|
||||
{
|
||||
var attributes = t.GetCustomAttributes(typeof (ContentImporterAttribute), false);
|
||||
if (attributes.Length != 0)
|
||||
{
|
||||
var importerAttribute = attributes[0] as ContentImporterAttribute;
|
||||
_importers.Add(new ImporterInfo
|
||||
{
|
||||
attribute = importerAttribute,
|
||||
type = t,
|
||||
assemblyTimestamp = assemblyTimestamp
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no attribute specify default one
|
||||
var importerAttribute = new ContentImporterAttribute(".*");
|
||||
importerAttribute.DefaultProcessor = "";
|
||||
importerAttribute.DisplayName = t.Name;
|
||||
_importers.Add(new ImporterInfo
|
||||
{
|
||||
attribute = importerAttribute,
|
||||
type = t,
|
||||
assemblyTimestamp = assemblyTimestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (t.GetInterface(@"IContentProcessor") != null)
|
||||
{
|
||||
var attributes = t.GetCustomAttributes(typeof (ContentProcessorAttribute), false);
|
||||
if (attributes.Length != 0)
|
||||
{
|
||||
var processorAttribute = attributes[0] as ContentProcessorAttribute;
|
||||
_processors.Add(new ProcessorInfo
|
||||
{
|
||||
attribute = processorAttribute,
|
||||
type = t,
|
||||
assemblyTimestamp = assemblyTimestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (t.GetInterface(@"ContentTypeWriter") != null)
|
||||
{
|
||||
// TODO: This doesn't work... how do i find these?
|
||||
_writers.Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Type[] GetImporterTypes()
|
||||
{
|
||||
if (_importers == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
List<Type> types = new List<Type>();
|
||||
|
||||
foreach (var item in _importers)
|
||||
{
|
||||
types.Add(item.type);
|
||||
}
|
||||
|
||||
return types.ToArray();
|
||||
}
|
||||
|
||||
public Type[] GetProcessorTypes()
|
||||
{
|
||||
if (_processors == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
List<Type> types = new List<Type>();
|
||||
|
||||
foreach (var item in _processors)
|
||||
{
|
||||
types.Add(item.type);
|
||||
}
|
||||
|
||||
return types.ToArray();
|
||||
}
|
||||
|
||||
public IContentImporter CreateImporter(string name)
|
||||
{
|
||||
if (_importers == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the importer.
|
||||
foreach (var info in _importers)
|
||||
{
|
||||
if (info.type.Name.Equals(name))
|
||||
return Activator.CreateInstance(info.type) as IContentImporter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string FindImporterByExtension(string ext)
|
||||
{
|
||||
if (_importers == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the importer.
|
||||
foreach (var info in _importers)
|
||||
{
|
||||
if (info.attribute.FileExtensions.Any(e => e.Equals(ext, StringComparison.InvariantCultureIgnoreCase)))
|
||||
return info.type.Name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public DateTime GetImporterAssemblyTimestamp(string name)
|
||||
{
|
||||
if (_importers == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the importer.
|
||||
foreach (var info in _importers)
|
||||
{
|
||||
if (info.type.Name.Equals(name))
|
||||
return info.assemblyTimestamp;
|
||||
}
|
||||
|
||||
return DateTime.MaxValue;
|
||||
}
|
||||
|
||||
public string FindDefaultProcessor(string importer)
|
||||
{
|
||||
if (_importers == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the importer.
|
||||
foreach (var info in _importers)
|
||||
{
|
||||
if (info.type.Name == importer)
|
||||
return info.attribute.DefaultProcessor;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Type GetProcessorType(string name)
|
||||
{
|
||||
if (_processors == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the processor type.
|
||||
foreach (var info in _processors)
|
||||
{
|
||||
if (info.type.Name.Equals(name))
|
||||
return info.type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveImporterAndProcessor(string sourceFilepath, ref string importerName, ref string processorName)
|
||||
{
|
||||
// Resolve the importer name.
|
||||
if (string.IsNullOrEmpty(importerName))
|
||||
importerName = FindImporterByExtension(Path.GetExtension(sourceFilepath));
|
||||
if (string.IsNullOrEmpty(importerName))
|
||||
throw new Exception(string.Format("Couldn't find a default importer for '{0}'!", sourceFilepath));
|
||||
|
||||
// Resolve the processor name.
|
||||
if (string.IsNullOrEmpty(processorName))
|
||||
processorName = FindDefaultProcessor(importerName);
|
||||
if (string.IsNullOrEmpty(processorName))
|
||||
throw new Exception(string.Format("Couldn't find a default processor for importer '{0}'!", importerName));
|
||||
}
|
||||
|
||||
public IContentProcessor CreateProcessor(string name, OpaqueDataDictionary processorParameters)
|
||||
{
|
||||
var processorType = GetProcessorType(name);
|
||||
if (processorType == null)
|
||||
return null;
|
||||
|
||||
// Create the processor.
|
||||
var processor = (IContentProcessor)Activator.CreateInstance(processorType);
|
||||
|
||||
// Convert and set the parameters on the processor.
|
||||
foreach (var param in processorParameters)
|
||||
{
|
||||
var propInfo = processorType.GetProperty(param.Key, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
|
||||
if (propInfo == null || propInfo.GetSetMethod(false) == null)
|
||||
continue;
|
||||
|
||||
// If the property value is already of the correct type then set it.
|
||||
if (propInfo.PropertyType.IsInstanceOfType(param.Value))
|
||||
propInfo.SetValue(processor, param.Value, null);
|
||||
else
|
||||
{
|
||||
// Find a type converter for this property.
|
||||
var typeConverter = TypeDescriptor.GetConverter(propInfo.PropertyType);
|
||||
if (typeConverter.CanConvertFrom(param.Value.GetType()))
|
||||
{
|
||||
var propValue = typeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, param.Value);
|
||||
propInfo.SetValue(processor, propValue, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default values for the content processor parameters.
|
||||
/// </summary>
|
||||
/// <param name="processorName">The name of the content processor.</param>
|
||||
/// <returns>
|
||||
/// A dictionary containing the default value for each parameter. Returns
|
||||
/// <see langword="null"/> if the content processor has not been created yet.
|
||||
/// </returns>
|
||||
public OpaqueDataDictionary GetProcessorDefaultValues(string processorName)
|
||||
{
|
||||
// null is not allowed as key in dictionary.
|
||||
if (processorName == null)
|
||||
processorName = string.Empty;
|
||||
|
||||
OpaqueDataDictionary defaultValues;
|
||||
if (!_processorDefaultValues.TryGetValue(processorName, out defaultValues))
|
||||
{
|
||||
// Create the content processor instance and read the default values.
|
||||
defaultValues = new OpaqueDataDictionary();
|
||||
var processorType = GetProcessorType(processorName);
|
||||
if (processorType != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processor = (IContentProcessor)Activator.CreateInstance(processorType);
|
||||
var properties = processorType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var property in properties)
|
||||
defaultValues.Add(property.Name, property.GetValue(processor, null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exception. Will be handled in ProcessContent.
|
||||
}
|
||||
}
|
||||
|
||||
_processorDefaultValues.Add(processorName, defaultValues);
|
||||
}
|
||||
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
public DateTime GetProcessorAssemblyTimestamp(string name)
|
||||
{
|
||||
if (_processors == null)
|
||||
ResolveAssemblies();
|
||||
|
||||
// Search for the processor.
|
||||
foreach (var info in _processors)
|
||||
{
|
||||
if (info.type.Name.Equals(name))
|
||||
return info.assemblyTimestamp;
|
||||
}
|
||||
|
||||
return DateTime.MaxValue;
|
||||
}
|
||||
|
||||
public OpaqueDataDictionary ValidateProcessorParameters(string name, OpaqueDataDictionary processorParameters)
|
||||
{
|
||||
var result = new OpaqueDataDictionary();
|
||||
|
||||
var processorType = GetProcessorType(name);
|
||||
if (processorType == null || processorParameters == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var param in processorParameters)
|
||||
{
|
||||
var propInfo = processorType.GetProperty(param.Key, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
|
||||
if (propInfo == null || propInfo.GetSetMethod(false) == null)
|
||||
continue;
|
||||
|
||||
// Make sure we can assign the value.
|
||||
if (!propInfo.PropertyType.IsInstanceOfType(param.Value))
|
||||
{
|
||||
// Make sure we can convert the value.
|
||||
var typeConverter = TypeDescriptor.GetConverter(propInfo.PropertyType);
|
||||
if (!typeConverter.CanConvertFrom(param.Value.GetType()))
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(param.Key, param.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ResolveOutputFilepath(string sourceFilepath, ref string outputFilepath)
|
||||
{
|
||||
// If the output path is null... build it from the source file path.
|
||||
if (string.IsNullOrEmpty(outputFilepath))
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(sourceFilepath) + ".xnb";
|
||||
var directory = PathHelper.GetRelativePath(ProjectDirectory,
|
||||
Path.GetDirectoryName(sourceFilepath) +
|
||||
Path.DirectorySeparatorChar);
|
||||
outputFilepath = Path.Combine(OutputDirectory, directory, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the extension is not XNB or the source file extension then add XNB.
|
||||
var sourceExt = Path.GetExtension(sourceFilepath);
|
||||
if (outputFilepath.EndsWith(sourceExt, StringComparison.InvariantCultureIgnoreCase))
|
||||
outputFilepath = outputFilepath.Substring(0, outputFilepath.Length - sourceExt.Length);
|
||||
if (!outputFilepath.EndsWith(".xnb", StringComparison.InvariantCultureIgnoreCase))
|
||||
outputFilepath += ".xnb";
|
||||
|
||||
// If the path isn't rooted then put it into the output directory.
|
||||
if (!Path.IsPathRooted(outputFilepath))
|
||||
outputFilepath = Path.Combine(OutputDirectory, outputFilepath);
|
||||
}
|
||||
|
||||
outputFilepath = PathHelper.Normalize(outputFilepath);
|
||||
}
|
||||
|
||||
private PipelineBuildEvent LoadBuildEvent(string destFile, out string eventFilepath)
|
||||
{
|
||||
var contentPath = Path.ChangeExtension(PathHelper.GetRelativePath(OutputDirectory, destFile), PipelineBuildEvent.Extension);
|
||||
eventFilepath = Path.Combine(IntermediateDirectory, contentPath);
|
||||
return PipelineBuildEvent.Load(eventFilepath);
|
||||
}
|
||||
|
||||
public void RegisterContent(string sourceFilepath, string outputFilepath = null, string importerName = null, string processorName = null, OpaqueDataDictionary processorParameters = null)
|
||||
{
|
||||
sourceFilepath = PathHelper.Normalize(sourceFilepath);
|
||||
ResolveOutputFilepath(sourceFilepath, ref outputFilepath);
|
||||
|
||||
ResolveImporterAndProcessor(sourceFilepath, ref importerName, ref processorName);
|
||||
|
||||
var contentEvent = new PipelineBuildEvent
|
||||
{
|
||||
SourceFile = sourceFilepath,
|
||||
DestFile = outputFilepath,
|
||||
Importer = importerName,
|
||||
Processor = processorName,
|
||||
Parameters = ValidateProcessorParameters(processorName, processorParameters),
|
||||
};
|
||||
|
||||
// Register pipeline build event. (Required to correctly resolve external dependencies.)
|
||||
TrackPipelineBuildEvent(contentEvent);
|
||||
}
|
||||
|
||||
public PipelineBuildEvent BuildContent(string sourceFilepath, string outputFilepath = null, string importerName = null, string processorName = null, OpaqueDataDictionary processorParameters = null)
|
||||
{
|
||||
sourceFilepath = PathHelper.Normalize(sourceFilepath);
|
||||
ResolveOutputFilepath(sourceFilepath, ref outputFilepath);
|
||||
|
||||
ResolveImporterAndProcessor(sourceFilepath, ref importerName, ref processorName);
|
||||
|
||||
// Record what we're building and how.
|
||||
var contentEvent = new PipelineBuildEvent
|
||||
{
|
||||
SourceFile = sourceFilepath,
|
||||
DestFile = outputFilepath,
|
||||
Importer = importerName,
|
||||
Processor = processorName,
|
||||
Parameters = ValidateProcessorParameters(processorName, processorParameters),
|
||||
};
|
||||
|
||||
// Load the previous content event if it exists.
|
||||
string eventFilepath;
|
||||
var cachedEvent = LoadBuildEvent(contentEvent.DestFile, out eventFilepath);
|
||||
|
||||
BuildContent(contentEvent, cachedEvent, eventFilepath);
|
||||
|
||||
return contentEvent;
|
||||
}
|
||||
|
||||
private void BuildContent(PipelineBuildEvent pipelineEvent, PipelineBuildEvent cachedEvent, string eventFilepath)
|
||||
{
|
||||
if (!File.Exists(pipelineEvent.SourceFile))
|
||||
{
|
||||
Logger.LogMessage("{0}", pipelineEvent.SourceFile);
|
||||
throw new PipelineException("The source file '{0}' does not exist!", pipelineEvent.SourceFile);
|
||||
}
|
||||
|
||||
Logger.PushFile(pipelineEvent.SourceFile);
|
||||
|
||||
// Keep track of all build events. (Required to resolve automatic names "AssetName_n".)
|
||||
TrackPipelineBuildEvent(pipelineEvent);
|
||||
|
||||
var rebuild = pipelineEvent.NeedsRebuild(this, cachedEvent);
|
||||
if (rebuild)
|
||||
Logger.LogMessage("{0}", pipelineEvent.SourceFile);
|
||||
else
|
||||
Logger.LogMessage("Skipping {0}", pipelineEvent.SourceFile);
|
||||
|
||||
Logger.Indent();
|
||||
try
|
||||
{
|
||||
if (!rebuild)
|
||||
{
|
||||
// While this asset doesn't need to be rebuilt the dependent assets might.
|
||||
foreach (var asset in cachedEvent.BuildAsset)
|
||||
{
|
||||
string assetEventFilepath;
|
||||
var assetCachedEvent = LoadBuildEvent(asset, out assetEventFilepath);
|
||||
|
||||
// If we cannot find the cached event for the dependancy
|
||||
// then we have to trigger a rebuild of the parent content.
|
||||
if (assetCachedEvent == null)
|
||||
{
|
||||
rebuild = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var depEvent = new PipelineBuildEvent
|
||||
{
|
||||
SourceFile = assetCachedEvent.SourceFile,
|
||||
DestFile = assetCachedEvent.DestFile,
|
||||
Importer = assetCachedEvent.Importer,
|
||||
Processor = assetCachedEvent.Processor,
|
||||
Parameters = assetCachedEvent.Parameters,
|
||||
};
|
||||
|
||||
// Give the asset a chance to rebuild.
|
||||
BuildContent(depEvent, assetCachedEvent, assetEventFilepath);
|
||||
}
|
||||
}
|
||||
|
||||
// Do we need to rebuild?
|
||||
if (rebuild)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
// Import and process the content.
|
||||
var processedObject = ProcessContent(pipelineEvent);
|
||||
|
||||
// Write the content to disk.
|
||||
WriteXnb(processedObject, pipelineEvent);
|
||||
|
||||
// Store the timestamp of the DLLs containing the importer and processor.
|
||||
pipelineEvent.ImporterTime = GetImporterAssemblyTimestamp(pipelineEvent.Importer);
|
||||
pipelineEvent.ProcessorTime = GetProcessorAssemblyTimestamp(pipelineEvent.Processor);
|
||||
|
||||
// Store the new event into the intermediate folder.
|
||||
pipelineEvent.Save(eventFilepath);
|
||||
|
||||
var buildTime = DateTime.UtcNow - startTime;
|
||||
|
||||
// Record stat for this file.
|
||||
ContentStats.RecordStats(pipelineEvent.SourceFile, pipelineEvent.DestFile, pipelineEvent.Processor, processedObject.GetType(), (float)buildTime.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the stats from the previous build.
|
||||
ContentStats.CopyPreviousStats(pipelineEvent.SourceFile);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.Unindent();
|
||||
Logger.PopFile();
|
||||
}
|
||||
}
|
||||
|
||||
public object ProcessContent(PipelineBuildEvent pipelineEvent)
|
||||
{
|
||||
if (!File.Exists(pipelineEvent.SourceFile))
|
||||
throw new PipelineException("The source file '{0}' does not exist!", pipelineEvent.SourceFile);
|
||||
|
||||
// Store the last write time of the source file
|
||||
// so we can detect if it has been changed.
|
||||
pipelineEvent.SourceTime = File.GetLastWriteTime(pipelineEvent.SourceFile);
|
||||
|
||||
// Make sure we can find the importer and processor.
|
||||
var importer = CreateImporter(pipelineEvent.Importer);
|
||||
if (importer == null)
|
||||
throw new PipelineException("Failed to create importer '{0}'", pipelineEvent.Importer);
|
||||
|
||||
// Try importing the content.
|
||||
object importedObject;
|
||||
if (RethrowExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var importContext = new PipelineImporterContext(this);
|
||||
importedObject = importer.Import(pipelineEvent.SourceFile, importContext);
|
||||
}
|
||||
catch (PipelineException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception inner)
|
||||
{
|
||||
throw new PipelineException(string.Format("Importer '{0}' had unexpected failure!", pipelineEvent.Importer), inner);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var importContext = new PipelineImporterContext(this);
|
||||
importedObject = importer.Import(pipelineEvent.SourceFile, importContext);
|
||||
}
|
||||
|
||||
// The pipelineEvent.Processor can be null or empty. In this case the
|
||||
// asset should be imported but not processed.
|
||||
if (string.IsNullOrEmpty(pipelineEvent.Processor))
|
||||
return importedObject;
|
||||
|
||||
var processor = CreateProcessor(pipelineEvent.Processor, pipelineEvent.Parameters);
|
||||
if (processor == null)
|
||||
throw new PipelineException("Failed to create processor '{0}'", pipelineEvent.Processor);
|
||||
|
||||
// Make sure the input type is valid.
|
||||
if (!processor.InputType.IsAssignableFrom(importedObject.GetType()))
|
||||
{
|
||||
throw new PipelineException(
|
||||
string.Format("The type '{0}' cannot be processed by {1} as a {2}!",
|
||||
importedObject.GetType().FullName,
|
||||
pipelineEvent.Processor,
|
||||
processor.InputType.FullName));
|
||||
}
|
||||
|
||||
// Process the imported object.
|
||||
|
||||
object processedObject;
|
||||
if (RethrowExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processContext = new PipelineProcessorContext(this, pipelineEvent);
|
||||
processedObject = processor.Process(importedObject, processContext);
|
||||
}
|
||||
catch (PipelineException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (InvalidContentException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception inner)
|
||||
{
|
||||
throw new PipelineException(string.Format("Processor '{0}' had unexpected failure!", pipelineEvent.Processor), inner);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var processContext = new PipelineProcessorContext(this, pipelineEvent);
|
||||
processedObject = processor.Process(importedObject, processContext);
|
||||
}
|
||||
|
||||
return processedObject;
|
||||
}
|
||||
|
||||
public void CleanContent(string sourceFilepath, string outputFilepath = null)
|
||||
{
|
||||
// First try to load the event file.
|
||||
ResolveOutputFilepath(sourceFilepath, ref outputFilepath);
|
||||
string eventFilepath;
|
||||
var cachedEvent = LoadBuildEvent(outputFilepath, out eventFilepath);
|
||||
|
||||
if (cachedEvent != null)
|
||||
{
|
||||
// Recursively clean additional (nested) assets.
|
||||
foreach (var asset in cachedEvent.BuildAsset)
|
||||
{
|
||||
string assetEventFilepath;
|
||||
var assetCachedEvent = LoadBuildEvent(asset, out assetEventFilepath);
|
||||
|
||||
if (assetCachedEvent == null)
|
||||
{
|
||||
Logger.LogMessage("Cleaning {0}", asset);
|
||||
|
||||
// Remove asset (.xnb file) from output folder.
|
||||
FileHelper.DeleteIfExists(asset);
|
||||
|
||||
// Remove event file (.mgcontent file) from intermediate folder.
|
||||
FileHelper.DeleteIfExists(assetEventFilepath);
|
||||
continue;
|
||||
}
|
||||
|
||||
CleanContent(string.Empty, asset);
|
||||
}
|
||||
|
||||
// Remove related output files (non-XNB files) that were copied to the output folder.
|
||||
foreach (var asset in cachedEvent.BuildOutput)
|
||||
{
|
||||
Logger.LogMessage("Cleaning {0}", asset);
|
||||
FileHelper.DeleteIfExists(asset);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogMessage("Cleaning {0}", outputFilepath);
|
||||
|
||||
// Remove asset (.xnb file) from output folder.
|
||||
FileHelper.DeleteIfExists(outputFilepath);
|
||||
|
||||
// Remove event file (.mgcontent file) from intermediate folder.
|
||||
FileHelper.DeleteIfExists(eventFilepath);
|
||||
|
||||
_pipelineBuildEvents.Remove(sourceFilepath);
|
||||
}
|
||||
|
||||
private void WriteXnb(object content, PipelineBuildEvent pipelineEvent)
|
||||
{
|
||||
// Make sure the output directory exists.
|
||||
var outputFileDir = Path.GetDirectoryName(pipelineEvent.DestFile);
|
||||
|
||||
Directory.CreateDirectory(outputFileDir);
|
||||
|
||||
if (_compiler == null)
|
||||
_compiler = new ContentCompiler();
|
||||
|
||||
// Write the XNB.
|
||||
using (var stream = new FileStream(pipelineEvent.DestFile, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
_compiler.Compile(stream, content, Platform, Profile, CompressContent, OutputDirectory, outputFileDir);
|
||||
|
||||
// Store the last write time of the output XNB here
|
||||
// so we can verify it hasn't been tampered with.
|
||||
pipelineEvent.DestTime = File.GetLastWriteTime(pipelineEvent.DestFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the pipeline build event (in memory) if no matching event is found.
|
||||
/// </summary>
|
||||
/// <param name="pipelineEvent">The pipeline build event.</param>
|
||||
private void TrackPipelineBuildEvent(PipelineBuildEvent pipelineEvent)
|
||||
{
|
||||
List<PipelineBuildEvent> pipelineBuildEvents;
|
||||
bool eventsFound = _pipelineBuildEvents.TryGetValue(pipelineEvent.SourceFile, out pipelineBuildEvents);
|
||||
if (!eventsFound)
|
||||
{
|
||||
pipelineBuildEvents = new List<PipelineBuildEvent>();
|
||||
_pipelineBuildEvents.Add(pipelineEvent.SourceFile, pipelineBuildEvents);
|
||||
}
|
||||
|
||||
if (FindMatchingEvent(pipelineBuildEvents, pipelineEvent.DestFile, pipelineEvent.Importer, pipelineEvent.Processor, pipelineEvent.Parameters) == null)
|
||||
pipelineBuildEvents.Add(pipelineEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an automatic asset name, such as "AssetName_0".
|
||||
/// </summary>
|
||||
/// <param name="sourceFileName">The source file name.</param>
|
||||
/// <param name="importerName">The name of the content importer. Can be <see langword="null"/>.</param>
|
||||
/// <param name="processorName">The name of the content processor. Can be <see langword="null"/>.</param>
|
||||
/// <param name="processorParameters">The processor parameters. Can be <see langword="null"/>.</param>
|
||||
/// <returns>The asset name.</returns>
|
||||
public string GetAssetName(string sourceFileName, string importerName, string processorName, OpaqueDataDictionary processorParameters)
|
||||
{
|
||||
Debug.Assert(Path.IsPathRooted(sourceFileName), "Absolute path expected.");
|
||||
|
||||
// Get source file name, which is used for lookup in _pipelineBuildEvents.
|
||||
sourceFileName = PathHelper.Normalize(sourceFileName);
|
||||
string relativeSourceFileName = PathHelper.GetRelativePath(ProjectDirectory, sourceFileName);
|
||||
|
||||
List<PipelineBuildEvent> pipelineBuildEvents;
|
||||
if (_pipelineBuildEvents.TryGetValue(sourceFileName, out pipelineBuildEvents))
|
||||
{
|
||||
// This source file has already been build.
|
||||
// --> Compare pipeline build events.
|
||||
ResolveImporterAndProcessor(sourceFileName, ref importerName, ref processorName);
|
||||
|
||||
var matchingEvent = FindMatchingEvent(pipelineBuildEvents, null, importerName, processorName, processorParameters);
|
||||
if (matchingEvent != null)
|
||||
{
|
||||
// Matching pipeline build event found.
|
||||
string existingName = matchingEvent.DestFile;
|
||||
existingName = PathHelper.GetRelativePath(OutputDirectory, existingName);
|
||||
existingName = existingName.Substring(0, existingName.Length - 4); // Remove ".xnb".
|
||||
return existingName;
|
||||
}
|
||||
|
||||
Logger.LogMessage(string.Format("Warning: Asset {0} built multiple times with different settings.", relativeSourceFileName));
|
||||
}
|
||||
|
||||
// No pipeline build event with matching settings found.
|
||||
// Get default asset name (= output file name relative to output folder without ".xnb").
|
||||
string directoryName = Path.GetDirectoryName(relativeSourceFileName);
|
||||
string fileName = Path.GetFileNameWithoutExtension(relativeSourceFileName);
|
||||
string assetName = Path.Combine(directoryName, fileName);
|
||||
assetName = PathHelper.Normalize(assetName);
|
||||
return AppendAssetNameSuffix(assetName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified list contains a matching pipeline build event.
|
||||
/// </summary>
|
||||
/// <param name="pipelineBuildEvents">The list of pipeline build events.</param>
|
||||
/// <param name="destFile">Absolute path to the output file. Can be <see langword="null"/>.</param>
|
||||
/// <param name="importerName">The name of the content importer. Can be <see langword="null"/>.</param>
|
||||
/// <param name="processorName">The name of the content processor. Can be <see langword="null"/>.</param>
|
||||
/// <param name="processorParameters">The processor parameters. Can be <see langword="null"/>.</param>
|
||||
/// <returns>
|
||||
/// The matching pipeline build event, or <see langword="null"/>.
|
||||
/// </returns>
|
||||
private PipelineBuildEvent FindMatchingEvent(List<PipelineBuildEvent> pipelineBuildEvents, string destFile, string importerName, string processorName, OpaqueDataDictionary processorParameters)
|
||||
{
|
||||
foreach (var existingBuildEvent in pipelineBuildEvents)
|
||||
{
|
||||
if ((destFile == null || existingBuildEvent.DestFile.Equals(destFile))
|
||||
&& existingBuildEvent.Importer == importerName
|
||||
&& existingBuildEvent.Processor == processorName)
|
||||
{
|
||||
var defaultValues = GetProcessorDefaultValues(processorName);
|
||||
if (PipelineBuildEvent.AreParametersEqual(existingBuildEvent.Parameters, processorParameters, defaultValues))
|
||||
{
|
||||
return existingBuildEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the asset name including a suffix, such as "_0". (The number is incremented
|
||||
/// automatically.
|
||||
/// </summary>
|
||||
/// <param name="baseAssetName">
|
||||
/// The asset name without suffix (relative to output folder).
|
||||
/// </param>
|
||||
/// <returns>The asset name with suffix.</returns>
|
||||
private string AppendAssetNameSuffix(string baseAssetName)
|
||||
{
|
||||
int index = 0;
|
||||
string assetName = baseAssetName + "_0";
|
||||
while (IsAssetNameUsed(assetName))
|
||||
{
|
||||
index++;
|
||||
assetName = baseAssetName + '_' + index;
|
||||
}
|
||||
|
||||
return assetName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified asset name is already used.
|
||||
/// </summary>
|
||||
/// <param name="assetName">The asset name (relative to output folder).</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the asset name is already used; otherwise,
|
||||
/// <see langword="false"/> if the name is available.
|
||||
/// </returns>
|
||||
private bool IsAssetNameUsed(string assetName)
|
||||
{
|
||||
string destFile = Path.Combine(OutputDirectory, assetName + ".xnb");
|
||||
|
||||
return _pipelineBuildEvents.SelectMany(pair => pair.Value)
|
||||
.Select(pipelineEvent => pipelineEvent.DestFile)
|
||||
.Any(existingDestFile => destFile.Equals(existingDestFile, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// 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 Microsoft.Xna.Framework.Content.Pipeline;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
public class PipelineProcessorContext : ContentProcessorContext
|
||||
{
|
||||
private readonly PipelineManager _manager;
|
||||
|
||||
private readonly PipelineBuildEvent _pipelineEvent;
|
||||
|
||||
public PipelineProcessorContext(PipelineManager manager, PipelineBuildEvent pipelineEvent)
|
||||
{
|
||||
_manager = manager;
|
||||
_pipelineEvent = pipelineEvent;
|
||||
}
|
||||
|
||||
public override TargetPlatform TargetPlatform { get { return _manager.Platform; } }
|
||||
public override GraphicsProfile TargetProfile { get { return _manager.Profile; } }
|
||||
|
||||
public override string BuildConfiguration { get { return _manager.Config; } }
|
||||
|
||||
public override string IntermediateDirectory { get { return _manager.IntermediateDirectory; } }
|
||||
public override string OutputDirectory { get { return _manager.OutputDirectory; } }
|
||||
public override string OutputFilename { get { return _pipelineEvent.DestFile; } }
|
||||
|
||||
public override OpaqueDataDictionary Parameters { get { return _pipelineEvent.Parameters; } }
|
||||
|
||||
public override ContentBuildLogger Logger { get { return _manager.Logger; } }
|
||||
|
||||
public override ContentIdentity SourceIdentity { get { return new ContentIdentity(_pipelineEvent.SourceFile); } }
|
||||
|
||||
public override void AddDependency(string filename)
|
||||
{
|
||||
_pipelineEvent.Dependencies.AddUnique(filename);
|
||||
}
|
||||
|
||||
public override void AddOutputFile(string filename)
|
||||
{
|
||||
_pipelineEvent.BuildOutput.AddUnique(filename);
|
||||
}
|
||||
|
||||
public override TOutput Convert<TInput, TOutput>( TInput input,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters)
|
||||
{
|
||||
var processor = _manager.CreateProcessor(processorName, processorParameters);
|
||||
var processContext = new PipelineProcessorContext(_manager, new PipelineBuildEvent { Parameters = processorParameters } );
|
||||
var processedObject = processor.Process(input, processContext);
|
||||
|
||||
// Add its dependencies and built assets to ours.
|
||||
_pipelineEvent.Dependencies.AddRangeUnique(processContext._pipelineEvent.Dependencies);
|
||||
_pipelineEvent.BuildAsset.AddRangeUnique(processContext._pipelineEvent.BuildAsset);
|
||||
|
||||
return (TOutput)processedObject;
|
||||
}
|
||||
|
||||
public override TOutput BuildAndLoadAsset<TInput, TOutput>( ExternalReference<TInput> sourceAsset,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters,
|
||||
string importerName)
|
||||
{
|
||||
var sourceFilepath = PathHelper.Normalize(sourceAsset.Filename);
|
||||
|
||||
// The processorName can be null or empty. In this case the asset should
|
||||
// be imported but not processed. This is, for example, necessary to merge
|
||||
// animation files as described here:
|
||||
// http://blogs.msdn.com/b/shawnhar/archive/2010/06/18/merging-animation-files.aspx.
|
||||
bool processAsset = !string.IsNullOrEmpty(processorName);
|
||||
_manager.ResolveImporterAndProcessor(sourceFilepath, ref importerName, ref processorName);
|
||||
|
||||
var buildEvent = new PipelineBuildEvent
|
||||
{
|
||||
SourceFile = sourceFilepath,
|
||||
Importer = importerName,
|
||||
Processor = processAsset ? processorName : null,
|
||||
Parameters = _manager.ValidateProcessorParameters(processorName, processorParameters),
|
||||
};
|
||||
|
||||
var processedObject = _manager.ProcessContent(buildEvent);
|
||||
|
||||
// Record that we processed this dependent asset.
|
||||
_pipelineEvent.Dependencies.AddUnique(sourceFilepath);
|
||||
|
||||
return (TOutput)processedObject;
|
||||
}
|
||||
|
||||
public override ExternalReference<TOutput> BuildAsset<TInput, TOutput>( ExternalReference<TInput> sourceAsset,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters,
|
||||
string importerName,
|
||||
string assetName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetName))
|
||||
assetName = _manager.GetAssetName(sourceAsset.Filename, importerName, processorName, processorParameters);
|
||||
|
||||
// Build the content.
|
||||
var buildEvent = _manager.BuildContent(sourceAsset.Filename, assetName, importerName, processorName, processorParameters);
|
||||
|
||||
// Record that we built this dependent asset.
|
||||
_pipelineEvent.BuildAsset.AddUnique(buildEvent.DestFile);
|
||||
|
||||
return new ExternalReference<TOutput>(buildEvent.DestFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
static public class TypeExtensions
|
||||
{
|
||||
public static Color ToColor(this System.Drawing.Color color)
|
||||
{
|
||||
return new Color(color.R, color.G, color.B, color.A);
|
||||
}
|
||||
|
||||
public static Vector3 ToVector3(this System.Drawing.Color color)
|
||||
{
|
||||
return new Vector3(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
|
||||
}
|
||||
|
||||
public static void AddUnique<T>(this List<T> list, T item)
|
||||
{
|
||||
if (!list.Contains(item))
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
public static void AddRangeUnique<T>(this List<T> dstList, List<T> list)
|
||||
{
|
||||
foreach (var i in list)
|
||||
{
|
||||
if (!dstList.Contains(i))
|
||||
dstList.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// 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.Drawing;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Framework.Content.Pipeline.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for serializing color types with the XmlSerializer.
|
||||
/// </summary>
|
||||
public class XmlColor
|
||||
{
|
||||
private Color _color;
|
||||
|
||||
public XmlColor()
|
||||
{
|
||||
}
|
||||
|
||||
public XmlColor(Color c)
|
||||
{
|
||||
_color = c;
|
||||
}
|
||||
|
||||
public static implicit operator Color(XmlColor x)
|
||||
{
|
||||
return x._color;
|
||||
}
|
||||
|
||||
public static implicit operator XmlColor(Color c)
|
||||
{
|
||||
return new XmlColor(c);
|
||||
}
|
||||
|
||||
public static string FromColor(Color color)
|
||||
{
|
||||
if (color.IsNamedColor)
|
||||
return color.Name;
|
||||
return string.Format("{0}, {1}, {2}, {3}", color.R, color.G, color.B, color.A);
|
||||
}
|
||||
|
||||
public static Color ToColor(string value)
|
||||
{
|
||||
if (!value.Contains(","))
|
||||
return Color.FromName(value);
|
||||
|
||||
int r, g, b, a;
|
||||
var colors = value.Split(',');
|
||||
int.TryParse(colors.Length > 0 ? colors[0] : string.Empty, out r);
|
||||
int.TryParse(colors.Length > 1 ? colors[1] : string.Empty, out g);
|
||||
int.TryParse(colors.Length > 2 ? colors[2] : string.Empty, out b);
|
||||
int.TryParse(colors.Length > 3 ? colors[3] : string.Empty, out a);
|
||||
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
[XmlText]
|
||||
public string Default
|
||||
{
|
||||
get { return FromColor(_color); }
|
||||
set { _color = ToColor(value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a collection of child objects for a content item.
|
||||
///
|
||||
/// Links from a child object to its parent are maintained as the collection contents are modified.
|
||||
/// </summary>
|
||||
/// <typeparam name="TParent"></typeparam>
|
||||
/// <typeparam name="TChild"></typeparam>
|
||||
public abstract class ChildCollection<TParent, TChild> : Collection<TChild>
|
||||
where TParent : class
|
||||
where TChild : class
|
||||
{
|
||||
TParent parent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of ChildCollection.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent object of the child objects returned in the collection.</param>
|
||||
protected ChildCollection(TParent parent)
|
||||
: base()
|
||||
{
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException("parent");
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all children from the collection.
|
||||
/// </summary>
|
||||
protected override void ClearItems()
|
||||
{
|
||||
// Remove parent reference from each child before clearing
|
||||
foreach (TChild child in this)
|
||||
SetParent(child, default(TParent));
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of a child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being retrieved.</param>
|
||||
/// <returns>The parent of the child object.</returns>
|
||||
protected abstract TParent GetParent(TChild child);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a child object into the collection at the specified location.
|
||||
/// </summary>
|
||||
/// <param name="index">The position in the collection.</param>
|
||||
/// <param name="item">The child object being inserted.</param>
|
||||
protected override void InsertItem(int index, TChild item)
|
||||
{
|
||||
// Make sure we have a
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("child");
|
||||
if (GetParent(item) != null)
|
||||
throw new InvalidOperationException("Child already has a parent");
|
||||
SetParent(item, parent);
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a child object from the collection.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item being removed.</param>
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
TChild child = this[index];
|
||||
SetParent(child, default(TParent));
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the value of the child object at the specified location.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the child object being modified.</param>
|
||||
/// <param name="item">The new value for the child object.</param>
|
||||
protected override void SetItem(int index, TChild item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("child");
|
||||
if (GetParent(item) != null)
|
||||
throw new InvalidOperationException("Child already has a parent");
|
||||
TChild child = this[index];
|
||||
SetParent(child, default(TParent));
|
||||
SetParent(item, parent);
|
||||
base.SetItem(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the value of the parent object of the specified child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being modified.</param>
|
||||
/// <param name="parent">The new value for the parent object.</param>
|
||||
protected abstract void SetParent(TChild child, TParent parent);
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// 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.IO;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for reporting informational messages or warnings from content importers and processors.
|
||||
/// Do not use this class to report errors. Instead, report errors by throwing a PipelineException or InvalidContentException.
|
||||
/// </summary>
|
||||
public abstract class ContentBuildLogger
|
||||
{
|
||||
Stack<string> filenames = new Stack<string>();
|
||||
private int indentCount = 0;
|
||||
|
||||
protected string IndentString { get { return String.Empty.PadLeft(Math.Max(0, indentCount), '\t'); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base reference path used when reporting errors during the content build process.
|
||||
/// </summary>
|
||||
public string LoggerRootDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentBuildLogger.
|
||||
/// </summary>
|
||||
protected ContentBuildLogger ()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the relative path to the filename from the root directory.
|
||||
/// </summary>
|
||||
/// <param name="filename">The target filename.</param>
|
||||
/// <param name="rootDirectory">The root directory. If not specified, the current directory is used.</param>
|
||||
/// <returns>The relative path.</returns>
|
||||
string GetRelativePath(string filename, string rootDirectory)
|
||||
{
|
||||
rootDirectory = Path.GetFullPath(string.IsNullOrEmpty(rootDirectory) ? "." : rootDirectory);
|
||||
filename = Path.GetFullPath(filename);
|
||||
if (filename.StartsWith(rootDirectory))
|
||||
return filename.Substring(rootDirectory.Length);
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filename currently being processed, for use in warning and error messages.
|
||||
/// </summary>
|
||||
/// <param name="contentIdentity">Identity of a content item. If specified, GetCurrentFilename uses this value to refine the search. If no value is specified, the current PushFile state is used.</param>
|
||||
/// <returns>Name of the file being processed.</returns>
|
||||
protected string GetCurrentFilename(
|
||||
ContentIdentity contentIdentity
|
||||
)
|
||||
{
|
||||
if ((contentIdentity != null) && !string.IsNullOrEmpty(contentIdentity.SourceFilename))
|
||||
return GetRelativePath(contentIdentity.SourceFilename, LoggerRootDirectory);
|
||||
if (filenames.Count > 0)
|
||||
return GetRelativePath(filenames.Peek(), LoggerRootDirectory);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a high-priority status message from a content importer or processor.
|
||||
/// </summary>
|
||||
/// <param name="message">Message being reported.</param>
|
||||
/// <param name="messageArgs">Arguments for the reported message.</param>
|
||||
public abstract void LogImportantMessage(
|
||||
string message,
|
||||
params Object[] messageArgs
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a low priority status message from a content importer or processor.
|
||||
/// </summary>
|
||||
/// <param name="message">Message being reported.</param>
|
||||
/// <param name="messageArgs">Arguments for the reported message.</param>
|
||||
public abstract void LogMessage(
|
||||
string message,
|
||||
params Object[] messageArgs
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a warning message from a content importer or processor.
|
||||
/// </summary>
|
||||
/// <param name="helpLink">Link to an existing online help topic containing related information.</param>
|
||||
/// <param name="contentIdentity">Identity of the content item that generated the message.</param>
|
||||
/// <param name="message">Message being reported.</param>
|
||||
/// <param name="messageArgs">Arguments for the reported message.</param>
|
||||
public abstract void LogWarning(
|
||||
string helpLink,
|
||||
ContentIdentity contentIdentity,
|
||||
string message,
|
||||
params Object[] messageArgs
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a message indicating that a content asset has completed processing.
|
||||
/// </summary>
|
||||
public void PopFile()
|
||||
{
|
||||
filenames.Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a message indicating that a content asset has begun processing.
|
||||
/// All logger warnings or error exceptions from this time forward to the next PopFile call refer to this file.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file containing future messages.</param>
|
||||
public void PushFile(string filename)
|
||||
{
|
||||
filenames.Push(filename);
|
||||
}
|
||||
|
||||
public void Indent()
|
||||
{
|
||||
indentCount++;
|
||||
}
|
||||
|
||||
public void Unindent()
|
||||
{
|
||||
indentCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties describing the origin of the game asset, such as the original source file and creation tool. This information is used for error reporting, and by processors that need to determine from what directory the asset was originally loaded.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ContentIdentity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the specific location of the content item within the larger source file.
|
||||
/// </summary>
|
||||
public string FragmentIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the file name of the asset source.
|
||||
/// </summary>
|
||||
public string SourceFilename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation tool of the asset.
|
||||
/// </summary>
|
||||
public string SourceTool { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentIdentity.
|
||||
/// </summary>
|
||||
public ContentIdentity()
|
||||
: this(string.Empty, string.Empty, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentIdentity with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="sourceFilename">The absolute path to the file name of the asset source.</param>
|
||||
public ContentIdentity(string sourceFilename)
|
||||
: this(sourceFilename, string.Empty, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentIdentity with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="sourceFilename">The absolute path to the file name of the asset source.</param>
|
||||
/// <param name="sourceTool">The name of the digital content creation (DCC) tool that created the asset.</param>
|
||||
public ContentIdentity(string sourceFilename, string sourceTool)
|
||||
: this(sourceFilename, sourceTool, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentIdentity with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="sourceFilename">The absolute path to the file name of the asset source.</param>
|
||||
/// <param name="sourceTool">The name of the digital content creation (DCC) tool that created the asset.</param>
|
||||
/// <param name="fragmentIdentifier">Specific location of the content item within the larger source file. For example, this could be a line number in the file.</param>
|
||||
public ContentIdentity(string sourceFilename, string sourceTool, string fragmentIdentifier)
|
||||
{
|
||||
SourceFilename = sourceFilename;
|
||||
SourceTool = sourceTool;
|
||||
FragmentIdentifier = fragmentIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a file format importer for use with game assets.
|
||||
/// Importers, either provided by the framework or written by a developer, must derive from ContentImporter, as well as being marked with a ContentImporterAttribute.
|
||||
/// An importer should produce results in the standard intermediate object model. If an asset has information not supported by the object model, the importer should output it as opaque data (key/value attributes attached to the relevant object). By following this procedure, a content pipeline can access specialized digital content creation (DCC) tool information, even when that information has not been fully standardized into the official object model.
|
||||
/// You can also design custom importers that accept and import types containing specific third-party extensions to the object model.
|
||||
/// </summary>
|
||||
public abstract class ContentImporter<T> : IContentImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentImporter.
|
||||
/// </summary>
|
||||
protected ContentImporter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the framework when importing a game asset. This is the method called by XNA when an asset is to be imported into an object that can be recognized by the Content Pipeline.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of a game asset file.</param>
|
||||
/// <param name="context">Contains information for importing a game asset, such as a logger interface.</param>
|
||||
/// <returns>Resulting game asset.</returns>
|
||||
public abstract T Import(string filename, ContentImporterContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called by the framework when importing a game asset. This is the method called by XNA when an asset is to be imported into an object that can be recognized by the Content Pipeline.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of a game asset file.</param>
|
||||
/// <param name="context">Contains information for importing a game asset, such as a logger interface.</param>
|
||||
/// <returns>Resulting game asset.</returns>
|
||||
Object IContentImporter.Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException("filename");
|
||||
if (context == null)
|
||||
throw new ArgumentNullException("context");
|
||||
return Import(filename, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties that identify and provide metadata about the importer, such as supported file extensions and caching information.
|
||||
/// Importers are required to initialize this attribute.
|
||||
/// </summary>
|
||||
public class ContentImporterAttribute : Attribute
|
||||
{
|
||||
List<string> extensions = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the caching of the content during importation.
|
||||
/// </summary>
|
||||
public bool CacheImportedData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the default processor for content read by this importer.
|
||||
/// </summary>
|
||||
public string DefaultProcessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string representing the importer in a user interface. This name is not used by the content pipeline and should not be passed to the BuildAssets task (a custom MSBuild task used by XNA Game Studio). It is used for display purposes only.
|
||||
/// </summary>
|
||||
public virtual string DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported file name extensions of the importer.
|
||||
/// </summary>
|
||||
public IEnumerable<string> FileExtensions { get { return extensions; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentImporterAttribute and sets the file name extension supported by the importer.
|
||||
/// </summary>
|
||||
/// <param name="fileExtension">The list of file name extensions supported by the importer. Prefix each extension with a '.'.</param>
|
||||
public ContentImporterAttribute(
|
||||
string fileExtension
|
||||
)
|
||||
{
|
||||
extensions.Add(fileExtension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentImporterAttribute and sets the file name extensions supported by the importer.
|
||||
/// </summary>
|
||||
/// <param name="fileExtensions">The list of file name extensions supported by the importer. Prefix each extension with a '.'.</param>
|
||||
public ContentImporterAttribute(
|
||||
params string[] fileExtensions
|
||||
)
|
||||
{
|
||||
extensions.AddRange(fileExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties that define logging behavior for the importer.
|
||||
/// </summary>
|
||||
public abstract class ContentImporterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The absolute path to the root of the build intermediate (object) directory.
|
||||
/// </summary>
|
||||
public abstract string IntermediateDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger for an importer.
|
||||
/// </summary>
|
||||
public abstract ContentBuildLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute path to the root of the build output (binaries) directory.
|
||||
/// </summary>
|
||||
public abstract string OutputDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentImporterContext.
|
||||
/// </summary>
|
||||
public ContentImporterContext()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dependency to the specified file. This causes a rebuild of the file, when modified, on subsequent incremental builds.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of an asset file.</param>
|
||||
public abstract void AddDependency(string filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties that define various aspects of content stored using the intermediate file format of the XNA Framework.
|
||||
/// </summary>
|
||||
public class ContentItem
|
||||
{
|
||||
OpaqueDataDictionary opaqueData = new OpaqueDataDictionary();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identity of the content item.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public ContentIdentity Identity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the content item.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opaque data of the content item.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public OpaqueDataDictionary OpaqueData { get { return opaqueData; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentItem.
|
||||
/// </summary>
|
||||
public ContentItem()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class to use when developing custom processor components. All processors must derive from this class.
|
||||
/// </summary>
|
||||
public abstract class ContentProcessor<TInput, TOutput> : IContentProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ContentProcessor class.
|
||||
/// </summary>
|
||||
protected ContentProcessor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the specified input data and returns the result.
|
||||
/// </summary>
|
||||
/// <param name="input">Existing content object being processed.</param>
|
||||
/// <param name="context">Contains any required custom process parameters.</param>
|
||||
/// <returns>A typed object representing the processed input.</returns>
|
||||
public abstract TOutput Process(TInput input, ContentProcessorContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expected object type of the input parameter to IContentProcessor.Process.
|
||||
/// </summary>
|
||||
Type IContentProcessor.InputType
|
||||
{
|
||||
get { return typeof(TInput); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object type returned by IContentProcessor.Process.
|
||||
/// </summary>
|
||||
Type IContentProcessor.OutputType
|
||||
{
|
||||
get { return typeof(TOutput); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the specified input data and returns the result.
|
||||
/// </summary>
|
||||
/// <param name="input">Existing content object being processed.</param>
|
||||
/// <param name="context">Contains any required custom process parameters.</param>
|
||||
/// <returns>The processed input.</returns>
|
||||
object IContentProcessor.Process(object input, ContentProcessorContext context)
|
||||
{
|
||||
if (input == null)
|
||||
throw new ArgumentNullException("input");
|
||||
if (context == null)
|
||||
throw new ArgumentNullException("context");
|
||||
if (!(input is TInput))
|
||||
throw new InvalidOperationException("input is not of the expected type");
|
||||
return Process((TInput)input, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets any existing content processor components.
|
||||
/// </summary>
|
||||
public class ContentProcessorAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the string representing the processor in a user interface. This name is not used by the content pipeline and should not be passed to the BuildAssets task (a custom MSBuild task used by XNA Game Studio). It is used for display purposes only.
|
||||
/// </summary>
|
||||
public virtual string DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of ContentProcessorAttribute.
|
||||
/// </summary>
|
||||
public ContentProcessorAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to custom processor parameters, methods for converting member data, and triggering nested builds.
|
||||
/// </summary>
|
||||
public abstract class ContentProcessorContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the current content build configuration.
|
||||
/// </summary>
|
||||
public abstract string BuildConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the directory that will contain any intermediate files generated by the content processor.
|
||||
/// </summary>
|
||||
public abstract string IntermediateDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger interface used for status messages or warnings.
|
||||
/// </summary>
|
||||
public abstract ContentBuildLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ContentIdentity representing the source asset imported.
|
||||
/// </summary>
|
||||
public abstract ContentIdentity SourceIdentity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output path of the content processor.
|
||||
/// </summary>
|
||||
public abstract string OutputDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output file name of the content processor.
|
||||
/// </summary>
|
||||
public abstract string OutputFilename { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of parameters used by the content processor.
|
||||
/// </summary>
|
||||
public abstract OpaqueDataDictionary Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current content build target platform.
|
||||
/// </summary>
|
||||
public abstract TargetPlatform TargetPlatform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current content build target profile.
|
||||
/// </summary>
|
||||
public abstract GraphicsProfile TargetProfile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ContentProcessorContext.
|
||||
/// </summary>
|
||||
public ContentProcessorContext()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dependency to the specified file. This causes a rebuild of the file, when modified, on subsequent incremental builds.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of an asset file.</param>
|
||||
public abstract void AddDependency(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Add a file name to the list of related output files maintained by the build item. This allows tracking build items that build multiple output files.
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file.</param>
|
||||
public abstract void AddOutputFile(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a nested build of the specified asset and then loads the result into memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the converted output.</typeparam>
|
||||
/// <param name="sourceAsset">Reference to the source asset.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <returns>Copy of the final converted content.</returns>
|
||||
/// <remarks>An example of usage would be a mesh processor calling BuildAndLoadAsset to build any associated textures and replace the original .tga file references with an embedded copy of the converted texture.</remarks>
|
||||
public TOutput BuildAndLoadAsset<TInput,TOutput>(
|
||||
ExternalReference<TInput> sourceAsset,
|
||||
string processorName
|
||||
)
|
||||
{
|
||||
return BuildAndLoadAsset<TInput, TOutput>(sourceAsset, processorName, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a nested build of the specified asset and then loads the result into memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the converted output.</typeparam>
|
||||
/// <param name="sourceAsset">Reference to the source asset.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <param name="processorParameters">Optional collection of named values available as input to the content processor.</param>
|
||||
/// <param name="importerName">Optional importer for this content.</param>
|
||||
/// <returns>Copy of the final converted content.</returns>
|
||||
/// <remarks>An example of usage would be a mesh processor calling BuildAndLoadAsset to build any associated textures and replace the original .tga file references with an embedded copy of the converted texture.</remarks>
|
||||
public abstract TOutput BuildAndLoadAsset<TInput,TOutput>(
|
||||
ExternalReference<TInput> sourceAsset,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters,
|
||||
string importerName
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a nested build of an additional asset.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the output.</typeparam>
|
||||
/// <param name="sourceAsset">Reference to the source asset.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <returns>Reference to the final compiled content. The build work is not required to complete before returning. Therefore, this file may not be up to date when BuildAsset returns but it will be available for loading by the game at runtime.</returns>
|
||||
/// <remarks>An example of usage for BuildAsset is being called by a mesh processor to request that any related textures used are also built, replacing the original TGA file references with new references to the converted texture files.</remarks>
|
||||
public ExternalReference<TOutput> BuildAsset<TInput,TOutput>(
|
||||
ExternalReference<TInput> sourceAsset,
|
||||
string processorName
|
||||
)
|
||||
{
|
||||
return BuildAsset<TInput, TOutput>(sourceAsset, processorName, null, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a nested build of an additional asset.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the output.</typeparam>
|
||||
/// <param name="sourceAsset">Reference to the source asset.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <param name="processorParameters">Optional collection of named values available as input to the content processor.</param>
|
||||
/// <param name="importerName">Optional importer for this content.</param>
|
||||
/// <param name="assetName">Optional name of the final compiled content.</param>
|
||||
/// <returns>Reference to the final compiled content. The build work is not required to complete before returning. Therefore, this file may not be up to date when BuildAsset returns but it will be available for loading by the game at runtime.</returns>
|
||||
/// <remarks>An example of usage for BuildAsset is being called by a mesh processor to request that any related textures used are also built, replacing the original TGA file references with new references to the converted texture files.</remarks>
|
||||
public abstract ExternalReference<TOutput> BuildAsset<TInput,TOutput>(
|
||||
ExternalReference<TInput> sourceAsset,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters,
|
||||
string importerName,
|
||||
string assetName
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a content item object using the specified content processor.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input content.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the converted output.</typeparam>
|
||||
/// <param name="input">Source content to be converted.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <returns>Reference of the final converted content.</returns>
|
||||
public TOutput Convert<TInput,TOutput>(
|
||||
TInput input,
|
||||
string processorName
|
||||
)
|
||||
{
|
||||
return Convert<TInput, TOutput>(input, processorName, new OpaqueDataDictionary());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a content item object using the specified content processor.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Type of the input content.</typeparam>
|
||||
/// <typeparam name="TOutput">Type of the converted output.</typeparam>
|
||||
/// <param name="input">Source content to be converted.</param>
|
||||
/// <param name="processorName">Optional processor for this content.</param>
|
||||
/// <param name="processorParameters">Optional parameters for the processor.</param>
|
||||
/// <returns>Reference of the final converted content.</returns>
|
||||
public abstract TOutput Convert<TInput,TOutput>(
|
||||
TInput input,
|
||||
string processorName,
|
||||
OpaqueDataDictionary processorParameters
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Content building statistics for a single source content file.
|
||||
/// </summary>
|
||||
public struct ContentStats
|
||||
{
|
||||
/// <summary>
|
||||
/// The absolute path to the source content file.
|
||||
/// </summary>
|
||||
public string SourceFile;
|
||||
|
||||
/// <summary>
|
||||
/// The absolute path to the destination content file.
|
||||
/// </summary>
|
||||
public string DestFile;
|
||||
|
||||
/// <summary>
|
||||
/// The content processor type name.
|
||||
/// </summary>
|
||||
public string ProcessorType;
|
||||
|
||||
/// <summary>
|
||||
/// The content type name.
|
||||
/// </summary>
|
||||
public string ContentType;
|
||||
|
||||
/// <summary>
|
||||
/// The source file size in bytes.
|
||||
/// </summary>
|
||||
public long SourceFileSize;
|
||||
|
||||
/// <summary>
|
||||
/// The destination file size in bytes.
|
||||
/// </summary>
|
||||
public long DestFileSize;
|
||||
|
||||
/// <summary>
|
||||
/// The content build time in seconds.
|
||||
/// </summary>
|
||||
public float BuildSeconds;
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of content building statistics for use in diagnosing content issues.
|
||||
/// </summary>
|
||||
public class ContentStatsCollection
|
||||
{
|
||||
private static readonly string _header = "Source File,Dest File,Processor Type,Content Type,Source File Size,Dest File Size,Build Seconds";
|
||||
private static readonly Regex _split = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
|
||||
|
||||
private readonly object _locker = new object();
|
||||
private readonly Dictionary<string, ContentStats> _statsBySource = new Dictionary<string, ContentStats>(1024);
|
||||
|
||||
public static readonly string Extension = ".mgstats";
|
||||
|
||||
/// <summary>
|
||||
/// Optionally used for copying stats that were stored in another collection.
|
||||
/// </summary>
|
||||
public ContentStatsCollection PreviousStats { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The internal content statistics dictionary.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, ContentStats> Stats
|
||||
{
|
||||
get { return _statsBySource; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the content statistics for a source file and returns true if found.
|
||||
/// </summary>
|
||||
public bool TryGetStats(string sourceFile, out ContentStats stats)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (!_statsBySource.TryGetValue(sourceFile, out stats))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all the content statistics.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
lock (_locker)
|
||||
_statsBySource.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store content building stats for a source file.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The absolute path to the source asset file.</param>
|
||||
/// <param name="destFile">The absolute path to the destination content file.</param>
|
||||
/// <param name="processorType">The type name of the content processor.</param>
|
||||
/// <param name="contentType">The content type object.</param>
|
||||
/// <param name="buildSeconds">The build time in seconds.</param>
|
||||
public void RecordStats(string sourceFile, string destFile, string processorType, Type contentType, float buildSeconds)
|
||||
{
|
||||
var sourceSize = new FileInfo(sourceFile).Length;
|
||||
var destSize = new FileInfo(destFile).Length;
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
ContentStats stats;
|
||||
_statsBySource.TryGetValue(sourceFile, out stats);
|
||||
|
||||
stats.SourceFile = sourceFile;
|
||||
stats.DestFile = destFile;
|
||||
|
||||
stats.SourceFileSize = sourceSize;
|
||||
stats.DestFileSize = destSize;
|
||||
|
||||
stats.ContentType = GetFriendlyTypeName(contentType);
|
||||
stats.ProcessorType = processorType;
|
||||
|
||||
stats.BuildSeconds = buildSeconds;
|
||||
|
||||
_statsBySource[stats.SourceFile] = stats;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy content building stats to the current collection from the PreviousStats.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The absolute path to the source asset file.</param>
|
||||
public void CopyPreviousStats(string sourceFile)
|
||||
{
|
||||
if (PreviousStats == null)
|
||||
return;
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_statsBySource.ContainsKey(sourceFile))
|
||||
return;
|
||||
|
||||
ContentStats stats;
|
||||
if (PreviousStats.TryGetStats(sourceFile, out stats))
|
||||
_statsBySource[stats.SourceFile] = stats;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFriendlyTypeName(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return "";
|
||||
if (type == typeof(int))
|
||||
return "int";
|
||||
else if (type == typeof(short))
|
||||
return "short";
|
||||
else if (type == typeof(byte))
|
||||
return "byte";
|
||||
else if (type == typeof(bool))
|
||||
return "bool";
|
||||
else if (type == typeof(long))
|
||||
return "long";
|
||||
else if (type == typeof(float))
|
||||
return "float";
|
||||
else if (type == typeof(double))
|
||||
return "double";
|
||||
else if (type == typeof(decimal))
|
||||
return "decimal";
|
||||
else if (type == typeof(string))
|
||||
return "string";
|
||||
else if (type.IsArray)
|
||||
return GetFriendlyTypeName(type.GetElementType()) + "[" + new string(',', type.GetArrayRank() - 1) + "]";
|
||||
else if (type.IsGenericType)
|
||||
return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => GetFriendlyTypeName(x)).ToArray()) + ">";
|
||||
else
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the content statistics from a folder.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The folder where the .mgstats file can be found.</param>
|
||||
/// <returns>Returns the content statistics or an empty collection.</returns>
|
||||
public static ContentStatsCollection Read(string outputPath)
|
||||
{
|
||||
var collection = new ContentStatsCollection();
|
||||
|
||||
var filePath = Path.Combine(outputPath, Extension);
|
||||
try
|
||||
{
|
||||
var lines = File.ReadAllLines(filePath);
|
||||
|
||||
// The first line is the CSV header... if it doesn't match then
|
||||
// assume the data is invalid or changed formats.
|
||||
if (lines[0] != _header)
|
||||
return collection;
|
||||
|
||||
for (var i = 1; i < lines.Length; i++)
|
||||
{
|
||||
var columns = _split.Split(lines[i]);
|
||||
if (columns.Length != 7)
|
||||
continue;
|
||||
|
||||
ContentStats stats;
|
||||
stats.SourceFile = columns[0].Trim('"');
|
||||
stats.DestFile = columns[1].Trim('"');
|
||||
stats.ProcessorType = columns[2].Trim('"');
|
||||
stats.ContentType = columns[3].Trim('"');
|
||||
stats.SourceFileSize = long.Parse(columns[4]);
|
||||
stats.DestFileSize = long.Parse(columns[5]);
|
||||
stats.BuildSeconds = float.Parse(columns[6]);
|
||||
|
||||
if (!collection._statsBySource.ContainsKey(stats.SourceFile))
|
||||
collection._statsBySource.Add(stats.SourceFile, stats);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Assume the file didn't exist or was incorrectly
|
||||
// formatted... either way we start from fresh data.
|
||||
collection.Reset();
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the content statistics to a folder with the .mgstats file name.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The folder to write the .mgstats file.</param>
|
||||
public void Write(string outputPath)
|
||||
{
|
||||
// ensure the output folder exists
|
||||
Directory.CreateDirectory(outputPath);
|
||||
var filePath = Path.Combine(outputPath, Extension);
|
||||
using (var textWriter = new StreamWriter(filePath, false, new UTF8Encoding(false)))
|
||||
{
|
||||
// Sort the items alphabetically to ensure a consistent output
|
||||
// and better mergability of the resulting file.
|
||||
var contentStats = _statsBySource.Values.OrderBy(c => c.SourceFile, StringComparer.InvariantCulture).ToList();
|
||||
|
||||
textWriter.WriteLine(_header);
|
||||
foreach (var stats in contentStats)
|
||||
textWriter.WriteLine("\"{0}\",\"{1}\",\"{2}\",\"{3}\",{4},{5},{6}", stats.SourceFile, stats.DestFile, stats.ProcessorType, stats.ContentType, stats.SourceFileSize, stats.DestFileSize, stats.BuildSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge in statistics from PreviousStats that do not exist in this collection.
|
||||
/// </summary>
|
||||
public void MergePreviousStats()
|
||||
{
|
||||
if (PreviousStats == null)
|
||||
return;
|
||||
|
||||
foreach (var stats in PreviousStats._statsBySource.Values)
|
||||
{
|
||||
if (!_statsBySource.ContainsKey(stats.SourceFile))
|
||||
_statsBySource.Add(stats.SourceFile, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
// 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.Graphics;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Loader class for DDS format image files.
|
||||
/// </summary>
|
||||
class DdsLoader
|
||||
{
|
||||
[Flags]
|
||||
enum Ddsd : uint
|
||||
{
|
||||
Caps = 0x1, // Required in every DDS file
|
||||
Height = 0x2, // Required in every DDS file
|
||||
Width = 0x4, // Required in every DDS file
|
||||
Pitch = 0x8, // Required when pitch is provided for an uncompressed texture
|
||||
PixelFormat = 0x1000, // Required in every DDS file
|
||||
MipMapCount = 0x2000, // Required in a mipmapped texture
|
||||
LinearSize = 0x80000, // Required when pitch is provided for a compressed texture
|
||||
Depth = 0x800000, // Required in a depth texture
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum DdsCaps : uint
|
||||
{
|
||||
Complex = 0x8, // Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture)
|
||||
MipMap = 0x400000, // Optional; should be used for a mipmap
|
||||
Texture = 0x1000, // Required
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum DdsCaps2 : uint
|
||||
{
|
||||
Cubemap = 0x200,
|
||||
CubemapPositiveX = 0x400,
|
||||
CubemapNegativeX = 0x800,
|
||||
CubemapPositiveY = 0x1000,
|
||||
CubemapNegativeY = 0x2000,
|
||||
CubemapPositiveZ = 0x4000,
|
||||
CubemapNegativeZ = 0x8000,
|
||||
Volume = 0x200000,
|
||||
|
||||
CubemapAllFaces = Cubemap | CubemapPositiveX | CubemapNegativeX | CubemapPositiveY | CubemapNegativeY | CubemapPositiveZ | CubemapNegativeZ,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum Ddpf : uint
|
||||
{
|
||||
AlphaPixels = 0x1,
|
||||
Alpha = 0x2,
|
||||
FourCC = 0x4,
|
||||
Rgb = 0x40,
|
||||
Yuv = 0x200,
|
||||
Luminance = 0x20000,
|
||||
}
|
||||
|
||||
static uint MakeFourCC(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
return ((uint)c1 << 24) | ((uint)c2 << 16) | ((uint)c3 << 8) | (uint)c4;
|
||||
}
|
||||
|
||||
static uint MakeFourCC(string cc)
|
||||
{
|
||||
return ((uint)cc[0] << 24) | ((uint)cc[1] << 16) | ((uint)cc[2] << 8) | (uint)cc[3];
|
||||
}
|
||||
|
||||
enum FourCC : uint
|
||||
{
|
||||
A32B32G32R32F = 116,
|
||||
Dxt1 = 0x31545844,
|
||||
Dxt2 = 0x32545844,
|
||||
Dxt3 = 0x33545844,
|
||||
Dxt4 = 0x34545844,
|
||||
Dxt5 = 0x35545844,
|
||||
Dx10 = 0x30315844,
|
||||
}
|
||||
|
||||
struct DdsPixelFormat
|
||||
{
|
||||
public uint dwSize;
|
||||
public Ddpf dwFlags;
|
||||
public FourCC dwFourCC;
|
||||
public uint dwRgbBitCount;
|
||||
public uint dwRBitMask;
|
||||
public uint dwGBitMask;
|
||||
public uint dwBBitMask;
|
||||
public uint dwABitMask;
|
||||
}
|
||||
|
||||
struct DdsHeader
|
||||
{
|
||||
public uint dwSize;
|
||||
public Ddsd dwFlags;
|
||||
public uint dwHeight;
|
||||
public uint dwWidth;
|
||||
public uint dwPitchOrLinearSize;
|
||||
public uint dwDepth;
|
||||
public uint dwMipMapCount;
|
||||
public DdsPixelFormat ddspf;
|
||||
public DdsCaps dwCaps;
|
||||
public DdsCaps2 dwCaps2;
|
||||
}
|
||||
|
||||
static SurfaceFormat GetSurfaceFormat(ref DdsPixelFormat pixelFormat, out bool rbSwap)
|
||||
{
|
||||
rbSwap = false;
|
||||
if (pixelFormat.dwFlags.HasFlag(Ddpf.FourCC))
|
||||
{
|
||||
switch (pixelFormat.dwFourCC)
|
||||
{
|
||||
case FourCC.A32B32G32R32F:
|
||||
return SurfaceFormat.Vector4;
|
||||
case FourCC.Dxt1:
|
||||
return SurfaceFormat.Dxt1;
|
||||
case FourCC.Dxt2:
|
||||
throw new ContentLoadException("Unsupported compression format DXT2");
|
||||
case FourCC.Dxt3:
|
||||
return SurfaceFormat.Dxt3;
|
||||
case FourCC.Dxt4:
|
||||
throw new ContentLoadException("Unsupported compression format DXT4");
|
||||
case FourCC.Dxt5:
|
||||
return SurfaceFormat.Dxt5;
|
||||
}
|
||||
}
|
||||
else if (pixelFormat.dwFlags.HasFlag(Ddpf.Rgb))
|
||||
{
|
||||
// Uncompressed format
|
||||
if (pixelFormat.dwFlags.HasFlag(Ddpf.AlphaPixels))
|
||||
{
|
||||
// Format contains RGB and A
|
||||
if (pixelFormat.dwRgbBitCount == 16)
|
||||
{
|
||||
if (pixelFormat.dwABitMask == 0xF)
|
||||
{
|
||||
rbSwap = pixelFormat.dwBBitMask == 0xF0;
|
||||
return SurfaceFormat.Bgra4444;
|
||||
}
|
||||
rbSwap = pixelFormat.dwBBitMask == 0x3E;
|
||||
return SurfaceFormat.Bgra5551;
|
||||
}
|
||||
else if (pixelFormat.dwRgbBitCount == 32)
|
||||
{
|
||||
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
||||
return SurfaceFormat.Color;
|
||||
}
|
||||
throw new ContentLoadException("Unsupported RGBA pixel format");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Format contains RGB only
|
||||
if (pixelFormat.dwRgbBitCount == 16)
|
||||
{
|
||||
rbSwap = pixelFormat.dwBBitMask == 0x1F;
|
||||
return SurfaceFormat.Bgr565;
|
||||
}
|
||||
else if (pixelFormat.dwRgbBitCount == 24)
|
||||
{
|
||||
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
||||
return SurfaceFormat.Color;
|
||||
}
|
||||
else if (pixelFormat.dwRgbBitCount == 32)
|
||||
{
|
||||
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
||||
return SurfaceFormat.Color;
|
||||
}
|
||||
throw new ContentLoadException("Unsupported RGB pixel format");
|
||||
}
|
||||
}
|
||||
//else if (pixelFormat.dwFlags.HasFlag(Ddpf.Luminance))
|
||||
//{
|
||||
// return SurfaceFormat.Alpha8;
|
||||
//}
|
||||
throw new ContentLoadException("Unsupported pixel format");
|
||||
}
|
||||
|
||||
static BitmapContent CreateBitmapContent(SurfaceFormat format, int width, int height)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.Color:
|
||||
return new PixelBitmapContent<Color>(width, height);
|
||||
|
||||
case SurfaceFormat.Bgra4444:
|
||||
return new PixelBitmapContent<Bgra4444>(width, height);
|
||||
|
||||
case SurfaceFormat.Bgra5551:
|
||||
return new PixelBitmapContent<Bgra5551>(width, height);
|
||||
|
||||
case SurfaceFormat.Bgr565:
|
||||
return new PixelBitmapContent<Bgr565>(width, height);
|
||||
|
||||
case SurfaceFormat.Dxt1:
|
||||
return new Dxt1BitmapContent(width, height);
|
||||
|
||||
case SurfaceFormat.Dxt3:
|
||||
return new Dxt3BitmapContent(width, height);
|
||||
|
||||
case SurfaceFormat.Dxt5:
|
||||
return new Dxt5BitmapContent(width, height);
|
||||
|
||||
case SurfaceFormat.Vector4:
|
||||
return new PixelBitmapContent<Vector4>(width, height);
|
||||
}
|
||||
throw new ContentLoadException("Unsupported SurfaceFormat " + format);
|
||||
}
|
||||
|
||||
static int GetBitmapSize(SurfaceFormat format, int width, int height)
|
||||
{
|
||||
// It is recommended that the dwPitchOrLinearSize field is ignored and we calculate it ourselves
|
||||
// https://msdn.microsoft.com/en-us/library/bb943991.aspx
|
||||
int pitch = 0;
|
||||
int rows = 0;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.Color:
|
||||
case SurfaceFormat.Bgra4444:
|
||||
case SurfaceFormat.Bgra5551:
|
||||
case SurfaceFormat.Bgr565:
|
||||
case SurfaceFormat.Vector4:
|
||||
pitch = width * format.GetSize();
|
||||
rows = height;
|
||||
break;
|
||||
|
||||
case SurfaceFormat.Dxt1:
|
||||
case SurfaceFormat.Dxt3:
|
||||
case SurfaceFormat.Dxt5:
|
||||
pitch = ((width + 3) / 4) * format.GetSize();
|
||||
rows = (height + 3) / 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ContentLoadException("Unsupported SurfaceFormat " + format);
|
||||
}
|
||||
|
||||
return pitch * rows;
|
||||
}
|
||||
|
||||
static internal TextureContent Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
var identity = new ContentIdentity(filename);
|
||||
TextureContent output = null;
|
||||
|
||||
using (var reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
||||
{
|
||||
// Read signature ("DDS ")
|
||||
var valid = reader.ReadByte() == 0x44;
|
||||
valid = valid && reader.ReadByte() == 0x44;
|
||||
valid = valid && reader.ReadByte() == 0x53;
|
||||
valid = valid && reader.ReadByte() == 0x20;
|
||||
if (!valid)
|
||||
throw new ContentLoadException("Invalid file signature");
|
||||
|
||||
var header = new DdsHeader();
|
||||
|
||||
// Read DDS_HEADER
|
||||
header.dwSize = reader.ReadUInt32();
|
||||
if (header.dwSize != 124)
|
||||
throw new ContentLoadException("Invalid DDS_HEADER dwSize value");
|
||||
header.dwFlags = (Ddsd)reader.ReadUInt32();
|
||||
header.dwHeight = reader.ReadUInt32();
|
||||
header.dwWidth = reader.ReadUInt32();
|
||||
header.dwPitchOrLinearSize = reader.ReadUInt32();
|
||||
header.dwDepth = reader.ReadUInt32();
|
||||
header.dwMipMapCount = reader.ReadUInt32();
|
||||
// The next 11 DWORDs are reserved and unused
|
||||
for (int i = 0; i < 11; ++i)
|
||||
reader.ReadUInt32();
|
||||
// Read DDS_PIXELFORMAT
|
||||
header.ddspf.dwSize = reader.ReadUInt32();
|
||||
if (header.ddspf.dwSize != 32)
|
||||
throw new ContentLoadException("Invalid DDS_PIXELFORMAT dwSize value");
|
||||
header.ddspf.dwFlags = (Ddpf)reader.ReadUInt32();
|
||||
header.ddspf.dwFourCC = (FourCC)reader.ReadUInt32();
|
||||
header.ddspf.dwRgbBitCount = reader.ReadUInt32();
|
||||
header.ddspf.dwRBitMask = reader.ReadUInt32();
|
||||
header.ddspf.dwGBitMask = reader.ReadUInt32();
|
||||
header.ddspf.dwBBitMask = reader.ReadUInt32();
|
||||
header.ddspf.dwABitMask = reader.ReadUInt32();
|
||||
// Continue reading DDS_HEADER
|
||||
header.dwCaps = (DdsCaps)reader.ReadUInt32();
|
||||
header.dwCaps2 = (DdsCaps2)reader.ReadUInt32();
|
||||
// dwCaps3 unused
|
||||
reader.ReadUInt32();
|
||||
// dwCaps4 unused
|
||||
reader.ReadUInt32();
|
||||
// dwReserved2 unused
|
||||
reader.ReadUInt32();
|
||||
|
||||
// Check for the existence of the DDS_HEADER_DXT10 struct next
|
||||
if (header.ddspf.dwFlags == Ddpf.FourCC && header.ddspf.dwFourCC == FourCC.Dx10)
|
||||
{
|
||||
throw new ContentLoadException("Unsupported DDS_HEADER_DXT10 struct found");
|
||||
}
|
||||
|
||||
int faceCount = 1;
|
||||
int mipMapCount = (int)(header.dwCaps.HasFlag(DdsCaps.MipMap) ? header.dwMipMapCount : 1);
|
||||
if (header.dwCaps2.HasFlag(DdsCaps2.Cubemap))
|
||||
{
|
||||
if (!header.dwCaps2.HasFlag(DdsCaps2.CubemapAllFaces))
|
||||
throw new ContentLoadException("Incomplete cubemap in DDS file");
|
||||
faceCount = 6;
|
||||
output = new TextureCubeContent() { Identity = identity };
|
||||
}
|
||||
else
|
||||
{
|
||||
output = new Texture2DContent() { Identity = identity };
|
||||
}
|
||||
|
||||
bool rbSwap;
|
||||
var format = GetSurfaceFormat(ref header.ddspf, out rbSwap);
|
||||
|
||||
for (int f = 0; f < faceCount; ++f)
|
||||
{
|
||||
var w = (int)header.dwWidth;
|
||||
var h = (int)header.dwHeight;
|
||||
var mipMaps = new MipmapChain();
|
||||
for (int m = 0; m < mipMapCount; ++m)
|
||||
{
|
||||
var content = CreateBitmapContent(format, w, h);
|
||||
var byteCount = GetBitmapSize(format, w, h);
|
||||
// A 24-bit format is slightly different
|
||||
if (header.ddspf.dwRgbBitCount == 24)
|
||||
byteCount = 3 * w * h;
|
||||
var bytes = reader.ReadBytes(byteCount);
|
||||
if (rbSwap)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.Bgr565:
|
||||
ByteSwapBGR565(bytes);
|
||||
break;
|
||||
case SurfaceFormat.Bgra4444:
|
||||
ByteSwapBGRA4444(bytes);
|
||||
break;
|
||||
case SurfaceFormat.Bgra5551:
|
||||
ByteSwapBGRA5551(bytes);
|
||||
break;
|
||||
case SurfaceFormat.Color:
|
||||
if (header.ddspf.dwRgbBitCount == 32)
|
||||
ByteSwapRGBX(bytes);
|
||||
else if (header.ddspf.dwRgbBitCount == 24)
|
||||
ByteSwapRGB(bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((format == SurfaceFormat.Color) && header.ddspf.dwFlags.HasFlag(Ddpf.Rgb) && !header.ddspf.dwFlags.HasFlag(Ddpf.AlphaPixels))
|
||||
{
|
||||
// Fill or add alpha with opaque
|
||||
if (header.ddspf.dwRgbBitCount == 32)
|
||||
ByteFillAlpha(bytes);
|
||||
else if (header.ddspf.dwRgbBitCount == 24)
|
||||
ByteExpandAlpha(ref bytes);
|
||||
}
|
||||
content.SetPixelData(bytes);
|
||||
mipMaps.Add(content);
|
||||
w = MathHelper.Max(1, w / 2);
|
||||
h = MathHelper.Max(1, h / 2);
|
||||
}
|
||||
output.Faces[f] = mipMaps;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static void ByteFillAlpha(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 4)
|
||||
{
|
||||
bytes[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
static void ByteExpandAlpha(ref byte[] bytes)
|
||||
{
|
||||
var rgba = new byte[bytes.Length + (bytes.Length / 3)];
|
||||
int j = 0;
|
||||
for (int i = 0; i < bytes.Length; i += 3)
|
||||
{
|
||||
rgba[j] = bytes[i];
|
||||
rgba[j + 1] = bytes[i + 1];
|
||||
rgba[j + 2] = bytes[i + 2];
|
||||
rgba[j + 3] = 255;
|
||||
j += 4;
|
||||
}
|
||||
bytes = rgba;
|
||||
}
|
||||
|
||||
static void ByteSwapRGB(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 3)
|
||||
{
|
||||
byte r = bytes[i];
|
||||
bytes[i] = bytes[i + 2];
|
||||
bytes[i + 2] = r;
|
||||
}
|
||||
}
|
||||
|
||||
static void ByteSwapRGBX(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 4)
|
||||
{
|
||||
byte r = bytes[i];
|
||||
bytes[i] = bytes[i + 2];
|
||||
bytes[i + 2] = r;
|
||||
}
|
||||
}
|
||||
|
||||
static void ByteSwapBGRA4444(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 2)
|
||||
{
|
||||
var r = bytes[i] & 0xF0;
|
||||
var b = bytes[i + 1] & 0xF0;
|
||||
bytes[i] = (byte)((bytes[i] & 0x0F) | b);
|
||||
bytes[i + 1] = (byte)((bytes[i + 1] & 0x0F) | r);
|
||||
}
|
||||
}
|
||||
|
||||
static void ByteSwapBGRA5551(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 2)
|
||||
{
|
||||
var r = (bytes[i] & 0xF8) >> 3;
|
||||
var b = (bytes[i + 1] & 0x3E) >> 1;
|
||||
bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3));
|
||||
bytes[i + 1] = (byte)((bytes[i + 1] & 0xC1) | (r << 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void ByteSwapBGR565(byte[] bytes)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 2)
|
||||
{
|
||||
var r = (bytes[i] & 0xF8) >> 3;
|
||||
var b = bytes[i + 1] & 0x1F;
|
||||
bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3));
|
||||
bytes[i + 1] = (byte)((bytes[i + 1] & 0xE0) | r);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteUncompressed(string filename, BitmapContent bitmapContent)
|
||||
{
|
||||
using (var writer = new BinaryWriter(new FileStream(filename, FileMode.Create, FileAccess.Write)))
|
||||
{
|
||||
// Write signature ("DDS ")
|
||||
writer.Write((byte)0x44);
|
||||
writer.Write((byte)0x44);
|
||||
writer.Write((byte)0x53);
|
||||
writer.Write((byte)0x20);
|
||||
|
||||
var header = new DdsHeader();
|
||||
header.dwSize = 124;
|
||||
header.dwFlags = Ddsd.Caps | Ddsd.Width | Ddsd.Height | Ddsd.Pitch | Ddsd.PixelFormat;
|
||||
header.dwWidth = (uint)bitmapContent.Width;
|
||||
header.dwHeight = (uint)bitmapContent.Height;
|
||||
header.dwPitchOrLinearSize = (uint)(bitmapContent.Width * 4);
|
||||
header.dwDepth = (uint)0;
|
||||
header.dwMipMapCount = (uint)0;
|
||||
|
||||
writer.Write((uint)header.dwSize);
|
||||
writer.Write((uint)header.dwFlags);
|
||||
writer.Write((uint)header.dwHeight);
|
||||
writer.Write((uint)header.dwWidth);
|
||||
writer.Write((uint)header.dwPitchOrLinearSize);
|
||||
writer.Write((uint)header.dwDepth);
|
||||
writer.Write((uint)header.dwMipMapCount);
|
||||
|
||||
// 11 unsed and reserved DWORDS.
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
|
||||
SurfaceFormat format;
|
||||
if (!bitmapContent.TryGetFormat(out format) || format != SurfaceFormat.Color)
|
||||
throw new NotSupportedException("Unsupported bitmap content!");
|
||||
|
||||
header.ddspf.dwSize = 32;
|
||||
header.ddspf.dwFlags = Ddpf.AlphaPixels | Ddpf.Rgb;
|
||||
header.ddspf.dwFourCC = 0;
|
||||
header.ddspf.dwRgbBitCount = 32;
|
||||
header.ddspf.dwRBitMask = 0x000000ff;
|
||||
header.ddspf.dwGBitMask = 0x0000ff00;
|
||||
header.ddspf.dwBBitMask = 0x00ff0000;
|
||||
header.ddspf.dwABitMask = 0xff000000;
|
||||
|
||||
// Write the DDS_PIXELFORMAT
|
||||
writer.Write((uint)header.ddspf.dwSize);
|
||||
writer.Write((uint)header.ddspf.dwFlags);
|
||||
writer.Write((uint)header.ddspf.dwFourCC);
|
||||
writer.Write((uint)header.ddspf.dwRgbBitCount);
|
||||
writer.Write((uint)header.ddspf.dwRBitMask);
|
||||
writer.Write((uint)header.ddspf.dwGBitMask);
|
||||
writer.Write((uint)header.ddspf.dwBBitMask);
|
||||
writer.Write((uint)header.ddspf.dwABitMask);
|
||||
|
||||
header.dwCaps = DdsCaps.Texture;
|
||||
header.dwCaps2 = 0;
|
||||
|
||||
// Continue reading DDS_HEADER
|
||||
writer.Write((uint)header.dwCaps);
|
||||
writer.Write((uint)header.dwCaps2);
|
||||
|
||||
// More reserved unused DWORDs.
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
writer.Write((uint)0);
|
||||
|
||||
// Write out the face data.
|
||||
writer.Write(bitmapContent.GetPixelData());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// 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.IO;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for reading effect (.fx) files for use in the Content Pipeline.
|
||||
/// </summary>
|
||||
[ContentImporter(".fx", DisplayName = "Effect Importer - MonoGame", DefaultProcessor = "EffectProcessor")]
|
||||
public class EffectImporter : ContentImporter<EffectContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of EffectImporter.
|
||||
/// </summary>
|
||||
public EffectImporter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the XNA Framework when importing an .fx file to be used as a game asset. This is the method called by the XNA Framework when an asset is to be imported into an object that can be recognized by the Content Pipeline.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of a game asset file.</param>
|
||||
/// <param name="context">Contains information for importing a game asset, such as a logger interface.</param>
|
||||
/// <returns>Resulting game asset.</returns>
|
||||
public override EffectContent Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
var effect = new EffectContent();
|
||||
effect.Identity = new ContentIdentity(filename);
|
||||
using (var reader = new StreamReader(filename))
|
||||
effect.EffectCode = reader.ReadToEnd();
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// 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;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies external references to a data file for the content item.
|
||||
///
|
||||
/// While the object model is instantiated, reference file names are absolute. When the file containing the external reference is serialized to disk, file names are relative to the file. This allows movement of the content tree to a different location without breaking internal links.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public sealed class ExternalReference<T> : ContentItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets and sets the file name of an ExternalReference.
|
||||
/// </summary>
|
||||
public string Filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ExternalReference.
|
||||
/// </summary>
|
||||
public ExternalReference()
|
||||
{
|
||||
Filename = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ExternalReference.
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the referenced file.</param>
|
||||
public ExternalReference(string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
throw new ArgumentNullException("filename");
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ExternalReference, specifying the file path relative to another content item.
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the referenced file.</param>
|
||||
/// <param name="relativeToContent">The content that the path specified in filename is relative to.</param>
|
||||
public ExternalReference(string filename, ContentIdentity relativeToContent)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
throw new ArgumentNullException("filename");
|
||||
if (relativeToContent == null)
|
||||
throw new ArgumentNullException("relativeToContent");
|
||||
if (string.IsNullOrEmpty(relativeToContent.SourceFilename))
|
||||
throw new ArgumentNullException("relativeToContent.SourceFilename");
|
||||
|
||||
// The intermediate serializer from XNA has the external reference
|
||||
// path walking up to the content project directory and then back
|
||||
// down to the asset path. We don't appear to have any way to do
|
||||
// that from here, so we'll work with the absolute path and let the
|
||||
// higher level process sort out any relative paths they need.
|
||||
var basePath = Path.GetDirectoryName(relativeToContent.SourceFilename);
|
||||
Filename = PathHelper.Normalize(Path.GetFullPath(Path.Combine(basePath, filename)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MonoGame.Utilities;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to run an external tool installed in the system. Useful for when
|
||||
/// we don't want to package the tool ourselves (ffmpeg) or it's provided
|
||||
/// by a third party (console manufacturer).
|
||||
/// </summary>
|
||||
internal class ExternalTool
|
||||
{
|
||||
public static int Run(string command, string arguments)
|
||||
{
|
||||
string stdout, stderr;
|
||||
var result = Run(command, arguments, out stdout, out stderr);
|
||||
if (result < 0)
|
||||
throw new Exception(string.Format("{0} returned exit code {1}", command, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int Run(string command, string arguments, out string stdout, out string stderr, string stdin = null)
|
||||
{
|
||||
// This particular case is likely to be the most common and thus
|
||||
// warrants its own specific error message rather than falling
|
||||
// back to a general exception from Process.Start()
|
||||
var fullPath = FindCommand(command);
|
||||
if (string.IsNullOrEmpty(fullPath))
|
||||
throw new Exception(string.Format("Couldn't locate external tool '{0}'.", command));
|
||||
|
||||
// We can't reference ref or out parameters from within
|
||||
// lambdas (for the thread functions), so we have to store
|
||||
// the data in a temporary variable and then assign these
|
||||
// variables to the out parameters.
|
||||
var stdoutTemp = string.Empty;
|
||||
var stderrTemp = string.Empty;
|
||||
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = arguments,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
FileName = fullPath,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
};
|
||||
|
||||
EnsureExecutable(fullPath);
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = processInfo;
|
||||
|
||||
process.Start();
|
||||
|
||||
// We have to run these in threads, because using ReadToEnd
|
||||
// on one stream can deadlock if the other stream's buffer is
|
||||
// full.
|
||||
var stdoutThread = new Thread(new ThreadStart(() =>
|
||||
{
|
||||
var memory = new MemoryStream();
|
||||
process.StandardOutput.BaseStream.CopyTo(memory);
|
||||
var bytes = new byte[memory.Position];
|
||||
memory.Seek(0, SeekOrigin.Begin);
|
||||
memory.Read(bytes, 0, bytes.Length);
|
||||
stdoutTemp = System.Text.Encoding.ASCII.GetString(bytes);
|
||||
}));
|
||||
var stderrThread = new Thread(new ThreadStart(() =>
|
||||
{
|
||||
var memory = new MemoryStream();
|
||||
process.StandardError.BaseStream.CopyTo(memory);
|
||||
var bytes = new byte[memory.Position];
|
||||
memory.Seek(0, SeekOrigin.Begin);
|
||||
memory.Read(bytes, 0, bytes.Length);
|
||||
stderrTemp = System.Text.Encoding.ASCII.GetString(bytes);
|
||||
}));
|
||||
|
||||
stdoutThread.Start();
|
||||
stderrThread.Start();
|
||||
|
||||
if (stdin != null)
|
||||
{
|
||||
process.StandardInput.Write(System.Text.Encoding.ASCII.GetBytes(stdin));
|
||||
}
|
||||
|
||||
// Make sure interactive prompts don't block.
|
||||
process.StandardInput.Close();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
stdoutThread.Join();
|
||||
stderrThread.Join();
|
||||
|
||||
stdout = stdoutTemp;
|
||||
stderr = stderrTemp;
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the fully-qualified path for a command, searching the system path if necessary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It's apparently necessary to use the full path when running on some systems.
|
||||
/// </remarks>
|
||||
private static string FindCommand(string command)
|
||||
{
|
||||
// Expand any environment variables.
|
||||
command = Environment.ExpandEnvironmentVariables(command);
|
||||
|
||||
// If we have a full path just pass it through.
|
||||
if (File.Exists(command))
|
||||
return command;
|
||||
|
||||
// We don't have a full path, so try running through the system path to find it.
|
||||
var paths = AppDomain.CurrentDomain.BaseDirectory +
|
||||
Path.PathSeparator +
|
||||
Environment.GetEnvironmentVariable("PATH");
|
||||
|
||||
var justTheName = Path.GetFileName(command);
|
||||
foreach (var path in paths.Split(Path.PathSeparator))
|
||||
{
|
||||
var fullName = Path.Combine(path, justTheName);
|
||||
if (File.Exists(fullName))
|
||||
return fullName;
|
||||
|
||||
if (CurrentPlatform.OS == OS.Windows)
|
||||
{
|
||||
var fullExeName = string.Concat(fullName, ".exe");
|
||||
if (File.Exists(fullExeName))
|
||||
return fullExeName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the specified executable has the executable bit set. If the
|
||||
/// executable doesn't have the executable bit set on Linux or Mac OS, then
|
||||
/// Mono will refuse to execute it.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path to the executable.</param>
|
||||
private static void EnsureExecutable(string path)
|
||||
{
|
||||
#if LINUX || MACOS
|
||||
if (path == "/bin/bash")
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var p = Process.Start("chmod", "u+x '" + path + "'");
|
||||
p.WaitForExit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// This platform may not have chmod in the path, in which case we can't
|
||||
// do anything reasonable here.
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely deletes the file if it exists.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The path to the file to delete.</param>
|
||||
public static void DeleteFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for reading AutoDesk (.fbx) files for use in the Content Pipeline.
|
||||
/// </summary>
|
||||
[ContentImporter(".fbx", DisplayName = "Fbx Importer - MonoGame", DefaultProcessor = "ModelProcessor")]
|
||||
public class FbxImporter : ContentImporter<NodeContent>
|
||||
{
|
||||
public override NodeContent Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException("filename");
|
||||
if (context == null)
|
||||
throw new ArgumentNullException("context");
|
||||
|
||||
var importer = new OpenAssetImporter("FbxImporter", true);
|
||||
return importer.Import(filename, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// 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.Xml;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for reading .spritefont files for use in the Content Pipeline.
|
||||
/// </summary>
|
||||
[ContentImporter(".spritefont", DisplayName = "Sprite Font Importer - MonoGame", DefaultProcessor = "FontDescriptionProcessor")]
|
||||
public class FontDescriptionImporter : ContentImporter<FontDescription>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of FontDescriptionImporter.
|
||||
/// </summary>
|
||||
public FontDescriptionImporter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the XNA Framework when importing a .spritefont file to be used as a game asset. This is the method called by the XNA Framework when an asset is to be imported into an object that can be recognized by the Content Pipeline.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of a game asset file.</param>
|
||||
/// <param name="context">Contains information for importing a game asset, such as a logger interface.</param>
|
||||
/// <returns>Resulting game asset.</returns>
|
||||
public override FontDescription Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
FontDescription fontDescription = null;
|
||||
|
||||
using (var input = XmlReader.Create(filename))
|
||||
fontDescription = IntermediateSerializer.Deserialize<FontDescription>(input, filename);
|
||||
|
||||
fontDescription.Identity = new ContentIdentity(new FileInfo(filename).FullName, "FontDescriptionImporter");
|
||||
|
||||
return fontDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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 Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class AlphaTestMaterialContent : MaterialContent
|
||||
{
|
||||
public const string AlphaKey = "Alpha";
|
||||
public const string AlphaFunctionKey = "AlphaFunction";
|
||||
public const string DiffuseColorKey = "DiffuseColor";
|
||||
public const string ReferenceAlphaKey = "ReferenceAlpha";
|
||||
public const string TextureKey = "Texture";
|
||||
public const string VertexColorEnabledKey = "VertexColorEnabled";
|
||||
|
||||
public float? Alpha
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(AlphaKey); }
|
||||
set { SetProperty(AlphaKey, value); }
|
||||
}
|
||||
|
||||
public CompareFunction? AlphaFunction
|
||||
{
|
||||
get { return GetValueTypeProperty<CompareFunction>(AlphaFunctionKey); }
|
||||
set { SetProperty(AlphaFunctionKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? DiffuseColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(DiffuseColorKey); }
|
||||
set { SetProperty(DiffuseColorKey, value); }
|
||||
}
|
||||
|
||||
public int? ReferenceAlpha
|
||||
{
|
||||
get { return GetValueTypeProperty<int>(ReferenceAlphaKey); }
|
||||
set { SetProperty(ReferenceAlphaKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture
|
||||
{
|
||||
get { return GetTexture(TextureKey); }
|
||||
set { SetTexture(TextureKey, value); }
|
||||
}
|
||||
|
||||
public bool? VertexColorEnabled
|
||||
{
|
||||
get { return GetValueTypeProperty<bool>(VertexColorEnabledKey); }
|
||||
set { SetProperty(VertexColorEnabledKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods and properties for maintaining an animation channel. An animation channel is a collection of keyframes describing the movement of a single bone or rigid object.
|
||||
/// </summary>
|
||||
public sealed class AnimationChannel : ICollection<AnimationKeyframe>, IEnumerable<AnimationKeyframe>, IEnumerable
|
||||
{
|
||||
List<AnimationKeyframe> keyframes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of keyframes in the collection.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return keyframes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe at the specified index position.
|
||||
/// </summary>
|
||||
public AnimationKeyframe this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return keyframes[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the object is read-only.
|
||||
/// </summary>
|
||||
bool ICollection<AnimationKeyframe>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AnimationChannel.
|
||||
/// </summary>
|
||||
public AnimationChannel()
|
||||
{
|
||||
keyframes = new List<AnimationKeyframe>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To satisfy ICollection
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
void ICollection<AnimationKeyframe>.Add(AnimationKeyframe item)
|
||||
{
|
||||
keyframes.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new keyframe to the collection, automatically sorting the contents according to keyframe times.
|
||||
/// </summary>
|
||||
/// <param name="item">Keyframe to be added to the channel.</param>
|
||||
/// <returns>Index of the new keyframe.</returns>
|
||||
public int Add(AnimationKeyframe item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
|
||||
// Find the correct place at which to insert it, so we can know the index to return.
|
||||
// The alternative is Add, Sort then return IndexOf, which would be horribly inefficient
|
||||
// and the order returned by Sort would change each time for keyframes with the same time.
|
||||
|
||||
// BinarySearch returns the index of the first item found with the same time, or the bitwise
|
||||
// complement of the next largest item found.
|
||||
int index = keyframes.BinarySearch(item);
|
||||
if (index >= 0)
|
||||
{
|
||||
// If a match is found, we do not know if it is at the start, middle or end of a range of
|
||||
// keyframes with the same time value. So look for the end of the range and insert there
|
||||
// so we have deterministic behaviour.
|
||||
while (index < keyframes.Count)
|
||||
{
|
||||
if (item.CompareTo(keyframes[index]) != 0)
|
||||
break;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If BinarySearch returns a negative value, it is the bitwise complement of the next largest
|
||||
// item in the list. So we just do a bitwise complement and insert at that index.
|
||||
index = ~index;
|
||||
}
|
||||
keyframes.Insert(index, item);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keyframes from the collection.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
keyframes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the collection for the specified keyframe.
|
||||
/// </summary>
|
||||
/// <param name="item">Keyframe being searched for.</param>
|
||||
/// <returns>true if the keyframe exists; false otherwise.</returns>
|
||||
public bool Contains(AnimationKeyframe item)
|
||||
{
|
||||
return keyframes.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To satisfy ICollection
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="arrayIndex"></param>
|
||||
void ICollection<AnimationKeyframe>.CopyTo(AnimationKeyframe[] array, int arrayIndex)
|
||||
{
|
||||
keyframes.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index for the specified keyframe.
|
||||
/// </summary>
|
||||
/// <param name="item">Identity of a keyframe.</param>
|
||||
/// <returns>Index of the specified keyframe.</returns>
|
||||
public int IndexOf(AnimationKeyframe item)
|
||||
{
|
||||
return keyframes.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified keyframe from the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">Keyframe being removed.</param>
|
||||
/// <returns>true if the keyframe was removed; false otherwise.</returns>
|
||||
public bool Remove(AnimationKeyframe item)
|
||||
{
|
||||
return keyframes.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the keyframe at the specified index position.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the keyframe being removed.</param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
keyframes.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the keyframes.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator for the keyframe collection.</returns>
|
||||
public IEnumerator<AnimationKeyframe> GetEnumerator()
|
||||
{
|
||||
return keyframes.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To satisfy ICollection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return keyframes.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of animation data channels, one per bone or rigid object.
|
||||
/// </summary>
|
||||
public sealed class AnimationChannelDictionary : NamedValueDictionary<AnimationChannel>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AnimationChannelDictionary.
|
||||
/// </summary>
|
||||
public AnimationChannelDictionary()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties for maintaining an animation.
|
||||
/// </summary>
|
||||
public class AnimationContent : ContentItem
|
||||
{
|
||||
AnimationChannelDictionary channels;
|
||||
TimeSpan duration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of animation data channels. Each channel describes the movement of a single bone or rigid object.
|
||||
/// </summary>
|
||||
public AnimationChannelDictionary Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total length of the animation.
|
||||
/// </summary>
|
||||
public TimeSpan Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
return duration;
|
||||
}
|
||||
set
|
||||
{
|
||||
duration = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AnimationContent.
|
||||
/// </summary>
|
||||
public AnimationContent()
|
||||
{
|
||||
channels = new AnimationChannelDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of named animations.
|
||||
/// </summary>
|
||||
public sealed class AnimationContentDictionary : NamedValueDictionary<AnimationContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AnimationContentDictionary.
|
||||
/// </summary>
|
||||
public AnimationContentDictionary()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// 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.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods and properties for managing a keyframe. A keyframe describes the position of an animation channel at a single point in time.
|
||||
/// </summary>
|
||||
public sealed class AnimationKeyframe : IComparable<AnimationKeyframe>
|
||||
{
|
||||
TimeSpan time;
|
||||
Matrix transform;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time offset from the start of the animation to the position described by this keyframe.
|
||||
/// </summary>
|
||||
public TimeSpan Time
|
||||
{
|
||||
get
|
||||
{
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position described by this keyframe.
|
||||
/// </summary>
|
||||
public Matrix Transform
|
||||
{
|
||||
get
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
set
|
||||
{
|
||||
transform = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AnimationKeyframe with the specified time offsetand transform.
|
||||
/// </summary>
|
||||
/// <param name="time">Time offset of the keyframe.</param>
|
||||
/// <param name="transform">Position of the keyframe.</param>
|
||||
public AnimationKeyframe(TimeSpan time, Matrix transform)
|
||||
{
|
||||
this.time = time;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this instance of a keyframe to another.
|
||||
/// </summary>
|
||||
/// <param name="other">Keyframe being compared to.</param>
|
||||
/// <returns>Indication of their relative values.</returns>
|
||||
public int CompareTo(AnimationKeyframe other)
|
||||
{
|
||||
// No sense in comparing the transform, so compare the time.
|
||||
// This would be used for sorting keyframes in time order.
|
||||
return time.CompareTo(other.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// 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.Graphics;
|
||||
using ATI.TextureConverter;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public abstract class AtcBitmapContent : BitmapContent
|
||||
{
|
||||
internal byte[] _bitmapData;
|
||||
|
||||
public AtcBitmapContent()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public AtcBitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
public override byte[] GetPixelData()
|
||||
{
|
||||
return _bitmapData;
|
||||
}
|
||||
|
||||
public override void SetPixelData(byte[] sourceData)
|
||||
{
|
||||
_bitmapData = sourceData;
|
||||
}
|
||||
|
||||
protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (format == sourceFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
SetPixelData(sourceBitmap.GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Destination region copy is not yet supported
|
||||
if (destinationRegion != new Rectangle(0, 0, Width, Height))
|
||||
return false;
|
||||
|
||||
// If the source is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(sourceBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, this, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to full colour 32-bit format. Floating point would be preferred for processing, but it appears the ATICompressor does not support this
|
||||
var colorBitmap = new PixelBitmapContent<Color>(sourceRegion.Width, sourceRegion.Height);
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, colorBitmap, new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height));
|
||||
sourceBitmap = colorBitmap;
|
||||
|
||||
ATICompressor.CompressionFormat targetFormat;
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.RgbaAtcExplicitAlpha:
|
||||
targetFormat = ATICompressor.CompressionFormat.AtcRgbaExplicitAlpha;
|
||||
break;
|
||||
case SurfaceFormat.RgbaAtcInterpolatedAlpha:
|
||||
targetFormat = ATICompressor.CompressionFormat.AtcRgbaInterpolatedAlpha;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
var sourceData = sourceBitmap.GetPixelData();
|
||||
var compressedData = ATICompressor.Compress(sourceData, Width, Height, targetFormat);
|
||||
SetPixelData(compressedData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (format == destinationFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
destinationBitmap.SetPixelData(GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// No other support for copying from a ATC texture yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class AtcExplicitBitmapContent : AtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of AtcExplicitBitmapContent.
|
||||
/// </summary>
|
||||
public AtcExplicitBitmapContent()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of AtcExplicitBitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public AtcExplicitBitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbaAtcExplicitAlpha;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "ATITC Explicit Alpha " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class AtcInterpolatedBitmapContent : AtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of AtcInterpolatedBitmapContent.
|
||||
/// </summary>
|
||||
public AtcInterpolatedBitmapContent()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of AtcInterpolatedBitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public AtcInterpolatedBitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbaAtcInterpolatedAlpha;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "ATITC Interpolated Alpha " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class BasicMaterialContent : MaterialContent
|
||||
{
|
||||
public const string AlphaKey = "Alpha";
|
||||
public const string DiffuseColorKey = "DiffuseColor";
|
||||
public const string EmissiveColorKey = "EmissiveColor";
|
||||
public const string SpecularColorKey = "SpecularColor";
|
||||
public const string SpecularPowerKey = "SpecularPower";
|
||||
public const string TextureKey = "Texture";
|
||||
public const string VertexColorEnabledKey = "VertexColorEnabled";
|
||||
|
||||
public float? Alpha
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(AlphaKey); }
|
||||
set { SetProperty(AlphaKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? DiffuseColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(DiffuseColorKey); }
|
||||
set { SetProperty(DiffuseColorKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? EmissiveColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(EmissiveColorKey); }
|
||||
set { SetProperty(EmissiveColorKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? SpecularColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(SpecularColorKey); }
|
||||
set { SetProperty(SpecularColorKey, value); }
|
||||
}
|
||||
|
||||
public float? SpecularPower
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(SpecularPowerKey); }
|
||||
set { SetProperty(SpecularPowerKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture
|
||||
{
|
||||
get { return GetTexture(TextureKey); }
|
||||
set { SetTexture(TextureKey, value); }
|
||||
}
|
||||
|
||||
public bool? VertexColorEnabled
|
||||
{
|
||||
get { return GetValueTypeProperty<bool>(VertexColorEnabledKey); }
|
||||
set { SetProperty(VertexColorEnabledKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties and methods for creating and maintaining a bitmap resource.
|
||||
/// </summary>
|
||||
public abstract class BitmapContent : ContentItem
|
||||
{
|
||||
int height;
|
||||
int width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the bitmap, in pixels.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return height;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException("height");
|
||||
height = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the bitmap, in pixels.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return width;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException("width");
|
||||
width = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BitmapContent.
|
||||
/// </summary>
|
||||
protected BitmapContent()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BitmapContent with the specified width or height.
|
||||
/// </summary>
|
||||
/// <param name="width">Width, in pixels, of the bitmap resource.</param>
|
||||
/// <param name="height">Height, in pixels, of the bitmap resource.</param>
|
||||
protected BitmapContent(int width, int height)
|
||||
{
|
||||
// Write to properties so validation is run.
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies one bitmap into another.
|
||||
/// The destination bitmap can be in any format and size. If the destination is larger or smaller, the source bitmap is scaled accordingly.
|
||||
/// </summary>
|
||||
/// <param name="sourceBitmap">BitmapContent being copied.</param>
|
||||
/// <param name="destinationBitmap">BitmapContent being overwritten.</param>
|
||||
public static void Copy(BitmapContent sourceBitmap, BitmapContent destinationBitmap)
|
||||
{
|
||||
if (sourceBitmap == null)
|
||||
throw new ArgumentNullException("sourceBitmap");
|
||||
if (destinationBitmap == null)
|
||||
throw new ArgumentNullException("destinationBitmap");
|
||||
|
||||
var sourceRegion = new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height);
|
||||
var destinationRegion = new Rectangle(0, 0, destinationBitmap.Width, destinationBitmap.Height);
|
||||
|
||||
Copy(sourceBitmap, sourceRegion, destinationBitmap, destinationRegion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies one bitmap into another.
|
||||
/// The destination bitmap can be in any format and size. If the destination is larger or smaller, the source bitmap is scaled accordingly.
|
||||
/// </summary>
|
||||
/// <param name="sourceBitmap">BitmapContent being copied.</param>
|
||||
/// <param name="sourceRegion">Region of sourceBitmap.</param>
|
||||
/// <param name="destinationBitmap">BitmapContent being overwritten.</param>
|
||||
/// <param name="destinationRegion">Region of bitmap to be overwritten.</param>
|
||||
public static void Copy(BitmapContent sourceBitmap, Rectangle sourceRegion, BitmapContent destinationBitmap, Rectangle destinationRegion)
|
||||
{
|
||||
ValidateCopyArguments(sourceBitmap, sourceRegion, destinationBitmap, destinationRegion);
|
||||
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
throw new InvalidOperationException("Could not retrieve surface format of source bitmap");
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
throw new InvalidOperationException("Could not retrieve surface format of destination bitmap");
|
||||
|
||||
// If the formats are the same and the regions are the full bounds of the bitmaps and they are the same size, do a simpler copy
|
||||
if (sourceFormat == destinationFormat && sourceRegion == destinationRegion
|
||||
&& sourceRegion == new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height)
|
||||
&& destinationRegion == new Rectangle(0, 0, destinationBitmap.Width, destinationBitmap.Height))
|
||||
{
|
||||
destinationBitmap.SetPixelData(sourceBitmap.GetPixelData());
|
||||
return;
|
||||
}
|
||||
|
||||
// The basic process is
|
||||
// 1. Copy from source bitmap region to a new PixelBitmapContent<Vector4> using sourceBitmap.TryCopyTo()
|
||||
// 2. If source and destination regions are a different size, resize Vector4 version
|
||||
// 3. Copy from Vector4 to destination region using destinationBitmap.TryCopyFrom()
|
||||
|
||||
// Copy from the source to the intermediate Vector4 format
|
||||
var intermediate = new PixelBitmapContent<Vector4>(sourceRegion.Width, sourceRegion.Height);
|
||||
var intermediateRegion = new Rectangle(0, 0, intermediate.Width, intermediate.Height);
|
||||
if (sourceBitmap.TryCopyTo(intermediate, sourceRegion, intermediateRegion))
|
||||
{
|
||||
// Resize the intermediate if required
|
||||
if (intermediate.Width != destinationRegion.Width || intermediate.Height != destinationRegion.Height)
|
||||
intermediate = intermediate.Resize(destinationRegion.Width, destinationRegion.Height) as PixelBitmapContent<Vector4>;
|
||||
// Copy from the intermediate to the destination
|
||||
if (destinationBitmap.TryCopyFrom(intermediate, new Rectangle(0, 0, intermediate.Width, intermediate.Height), destinationRegion))
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, one of the above steps didn't work
|
||||
throw new InvalidOperationException("Could not copy between " + sourceFormat + " and " + destinationFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads encoded bitmap content.
|
||||
/// </summary>
|
||||
/// <returns>Array containing encoded bitmap data.</returns>
|
||||
public abstract byte[] GetPixelData();
|
||||
|
||||
/// <summary>
|
||||
/// Writes encoded bitmap content.
|
||||
/// </summary>
|
||||
/// <param name="sourceData">Array containing encoded bitmap data to be set.</param>
|
||||
public abstract void SetPixelData(byte[] sourceData);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap resource.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}, {1}x{2}", GetType().Name, Width, Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy a region from a specified bitmap.
|
||||
/// </summary>
|
||||
/// <param name="sourceBitmap">BitmapContent being copied.</param>
|
||||
/// <param name="sourceRegion">Location of sourceBitmap.</param>
|
||||
/// <param name="destinationRegion">Region of destination bitmap to be overwritten.</param>
|
||||
/// <returns>true if region copy is supported; false otherwise.</returns>
|
||||
protected abstract bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy a region of the specified bitmap onto another.
|
||||
/// </summary>
|
||||
/// <param name="destinationBitmap">BitmapContent being overwritten.</param>
|
||||
/// <param name="sourceRegion">Location of the source bitmap.</param>
|
||||
/// <param name="destinationRegion">Region of destination bitmap to be overwritten.</param>
|
||||
/// <returns>true if region copy is supported; false otherwise.</returns>
|
||||
protected abstract bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public abstract bool TryGetFormat(out SurfaceFormat format);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the arguments to the Copy function.
|
||||
/// </summary>
|
||||
/// <param name="sourceBitmap">BitmapContent being copied.</param>
|
||||
/// <param name="sourceRegion">Location of sourceBitmap.</param>
|
||||
/// <param name="destinationBitmap">BitmapContent being overwritten.</param>
|
||||
/// <param name="destinationRegion">Region of bitmap to be overwritten.</param>
|
||||
protected static void ValidateCopyArguments(BitmapContent sourceBitmap, Rectangle sourceRegion, BitmapContent destinationBitmap, Rectangle destinationRegion)
|
||||
{
|
||||
if (sourceBitmap == null)
|
||||
throw new ArgumentNullException("sourceBitmap");
|
||||
if (destinationBitmap == null)
|
||||
throw new ArgumentNullException("destinationBitmap");
|
||||
// Make sure regions are within the bounds of the bitmaps
|
||||
if (sourceRegion.Left < 0
|
||||
|| sourceRegion.Top < 0
|
||||
|| sourceRegion.Width <= 0
|
||||
|| sourceRegion.Height <= 0
|
||||
|| sourceRegion.Right > sourceBitmap.Width
|
||||
|| sourceRegion.Bottom > sourceBitmap.Height)
|
||||
throw new ArgumentOutOfRangeException("sourceRegion");
|
||||
if (destinationRegion.Left < 0
|
||||
|| destinationRegion.Top < 0
|
||||
|| destinationRegion.Width <= 0
|
||||
|| destinationRegion.Height <= 0
|
||||
|| destinationRegion.Right > destinationBitmap.Width
|
||||
|| destinationRegion.Bottom > destinationBitmap.Height)
|
||||
throw new ArgumentOutOfRangeException("destinationRegion");
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an animation skeleton.
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerDisplay("Bone '{Name}'")]
|
||||
public class BoneContent : NodeContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BoneContent.
|
||||
/// </summary>
|
||||
public BoneContent()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties for managing a bone weight.
|
||||
/// </summary>
|
||||
public struct BoneWeight
|
||||
{
|
||||
string boneName;
|
||||
float weight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the bone.
|
||||
/// </summary>
|
||||
public string BoneName
|
||||
{
|
||||
get
|
||||
{
|
||||
return boneName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of bone influence, ranging from zero to one. The complete set of weights in a BoneWeightCollection should sum to one.
|
||||
/// </summary>
|
||||
public float Weight
|
||||
{
|
||||
get
|
||||
{
|
||||
return weight;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
weight = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BoneWeight with the specified name and weight.
|
||||
/// </summary>
|
||||
/// <param name="boneName">Name of the bone.</param>
|
||||
/// <param name="weight">Amount of influence, ranging from zero to one.</param>
|
||||
public BoneWeight(string boneName, float weight)
|
||||
{
|
||||
this.boneName = boneName;
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of bone weights of a vertex.
|
||||
/// </summary>
|
||||
public sealed class BoneWeightCollection : Collection<BoneWeight>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BoneWeightCollection.
|
||||
/// </summary>
|
||||
public BoneWeightCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the contents of the weights list.
|
||||
/// </summary>
|
||||
public void NormalizeWeights()
|
||||
{
|
||||
// Normalization does the following:
|
||||
//
|
||||
// - Sorts weights such that the most significant weight is first.
|
||||
// - Removes zero-value entries.
|
||||
// - Adjusts values so the sum equals one.
|
||||
//
|
||||
// Throws InvalidContentException if all weights are zero.
|
||||
NormalizeWeights(int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the contents of the bone weights list.
|
||||
/// </summary>
|
||||
/// <param name="maxWeights">Maximum number of weights allowed.</param>
|
||||
public void NormalizeWeights(int maxWeights)
|
||||
{
|
||||
// Normalization does the following:
|
||||
//
|
||||
// - Sorts weights such that the most significant weight is first.
|
||||
// - Removes zero-value entries.
|
||||
// - Discards weights with the smallest value until there are maxWeights or less in the list.
|
||||
// - Adjusts values so the sum equals one.
|
||||
//
|
||||
// Throws InvalidContentException if all weights are zero.
|
||||
|
||||
var weights = (List<BoneWeight>)Items;
|
||||
|
||||
// Sort into descending order
|
||||
weights.Sort((b1, b2) => b2.Weight.CompareTo(b1.Weight));
|
||||
|
||||
// Find the sum to validate we have weights and to normalize the weights
|
||||
float sum = 0.0f;
|
||||
int index = 0;
|
||||
// Cannot use a foreach or for because the index may not always increment and the length of the list may change.
|
||||
while (index < weights.Count)
|
||||
{
|
||||
float weight = weights[index].Weight;
|
||||
if ((weight > 0.0f) && (index < maxWeights))
|
||||
{
|
||||
sum += weight;
|
||||
++index;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Discard any zero weights or if we have exceeded the maximum number of weights
|
||||
weights.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (sum == 0.0f)
|
||||
throw new InvalidContentException("Total bone weights in a collection must not be zero");
|
||||
|
||||
// Normalize each weight
|
||||
int count = weights.Count();
|
||||
// Old-school trick. Multiplication is faster than division, so multiply by the inverse.
|
||||
float invSum = 1.0f / sum;
|
||||
for (index = 0; index < count; ++index)
|
||||
{
|
||||
BoneWeight bw = weights[index];
|
||||
bw.Weight *= invSum;
|
||||
weights[index] = bw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// 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.Processors;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
internal class DefaultTextureProfile : TextureProfile
|
||||
{
|
||||
public override bool Supports(TargetPlatform platform)
|
||||
{
|
||||
return platform == TargetPlatform.Android ||
|
||||
platform == TargetPlatform.DesktopGL ||
|
||||
platform == TargetPlatform.MacOSX ||
|
||||
platform == TargetPlatform.NativeClient ||
|
||||
platform == TargetPlatform.RaspberryPi ||
|
||||
platform == TargetPlatform.Windows ||
|
||||
platform == TargetPlatform.WindowsPhone8 ||
|
||||
platform == TargetPlatform.WindowsStoreApp ||
|
||||
platform == TargetPlatform.iOS;
|
||||
}
|
||||
|
||||
private static bool IsCompressedTextureFormat(TextureProcessorOutputFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureProcessorOutputFormat.AtcCompressed:
|
||||
case TextureProcessorOutputFormat.DxtCompressed:
|
||||
case TextureProcessorOutputFormat.Etc1Compressed:
|
||||
case TextureProcessorOutputFormat.PvrCompressed:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static TextureProcessorOutputFormat GetTextureFormatForPlatform(TextureProcessorOutputFormat format, TargetPlatform platform)
|
||||
{
|
||||
// Select the default texture compression format for the target platform
|
||||
if (format == TextureProcessorOutputFormat.Compressed)
|
||||
{
|
||||
if (platform == TargetPlatform.iOS)
|
||||
format = TextureProcessorOutputFormat.PvrCompressed;
|
||||
else if (platform == TargetPlatform.Android)
|
||||
format = TextureProcessorOutputFormat.Etc1Compressed;
|
||||
else
|
||||
format = TextureProcessorOutputFormat.DxtCompressed;
|
||||
}
|
||||
|
||||
if (IsCompressedTextureFormat(format))
|
||||
{
|
||||
// Make sure the target platform supports the selected texture compression format
|
||||
if (platform == TargetPlatform.iOS)
|
||||
{
|
||||
if (format != TextureProcessorOutputFormat.PvrCompressed)
|
||||
throw new PlatformNotSupportedException("iOS platform only supports PVR texture compression");
|
||||
}
|
||||
else if (platform == TargetPlatform.Windows ||
|
||||
platform == TargetPlatform.WindowsPhone8 ||
|
||||
platform == TargetPlatform.WindowsStoreApp ||
|
||||
platform == TargetPlatform.DesktopGL ||
|
||||
platform == TargetPlatform.MacOSX ||
|
||||
platform == TargetPlatform.NativeClient)
|
||||
{
|
||||
if (format != TextureProcessorOutputFormat.DxtCompressed)
|
||||
throw new PlatformNotSupportedException(format + " platform only supports DXT texture compression");
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public override void Requirements(ContentProcessorContext context, TextureProcessorOutputFormat format, out bool requiresPowerOfTwo, out bool requiresSquare)
|
||||
{
|
||||
if (format == TextureProcessorOutputFormat.Compressed)
|
||||
format = GetTextureFormatForPlatform(format, context.TargetPlatform);
|
||||
|
||||
// Does it require POT textures?
|
||||
switch (format)
|
||||
{
|
||||
default:
|
||||
requiresPowerOfTwo = false;
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.DxtCompressed:
|
||||
requiresPowerOfTwo = context.TargetProfile == GraphicsProfile.Reach;
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.PvrCompressed:
|
||||
case TextureProcessorOutputFormat.Etc1Compressed:
|
||||
requiresPowerOfTwo = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Does it require square textures?
|
||||
switch (format)
|
||||
{
|
||||
default:
|
||||
requiresSquare = false;
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.PvrCompressed:
|
||||
requiresSquare = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PlatformCompressTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool isSpriteFont)
|
||||
{
|
||||
format = GetTextureFormatForPlatform(format, context.TargetPlatform);
|
||||
|
||||
// Make sure we're in a floating point format
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Vector4>));
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case TextureProcessorOutputFormat.AtcCompressed:
|
||||
GraphicsUtil.CompressAti(context, content, isSpriteFont);
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.Color16Bit:
|
||||
GraphicsUtil.CompressColor16Bit(context, content);
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.DxtCompressed:
|
||||
GraphicsUtil.CompressDxt(context, content, isSpriteFont);
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.Etc1Compressed:
|
||||
GraphicsUtil.CompressEtc1(context, content, isSpriteFont);
|
||||
break;
|
||||
|
||||
case TextureProcessorOutputFormat.PvrCompressed:
|
||||
GraphicsUtil.CompressPvrtc(context, content, isSpriteFont);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class DualTextureMaterialContent : MaterialContent
|
||||
{
|
||||
public const string AlphaKey = "Alpha";
|
||||
public const string DiffuseColorKey = "DiffuseColor";
|
||||
public const string TextureKey = "Texture";
|
||||
public const string Texture2Key = "Texture2";
|
||||
public const string VertexColorEnabledKey = "VertexColorEnabled";
|
||||
|
||||
public float? Alpha
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(AlphaKey); }
|
||||
set { SetProperty(AlphaKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? DiffuseColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(DiffuseColorKey); }
|
||||
set { SetProperty(DiffuseColorKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture
|
||||
{
|
||||
get { return GetTexture(TextureKey); }
|
||||
set { SetTexture(TextureKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture2
|
||||
{
|
||||
get { return GetTexture(Texture2Key); }
|
||||
set { SetTexture(Texture2Key, value); }
|
||||
}
|
||||
|
||||
public bool? VertexColorEnabled
|
||||
{
|
||||
get { return GetValueTypeProperty<bool>(VertexColorEnabledKey); }
|
||||
set { SetProperty(VertexColorEnabledKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class Dxt1BitmapContent : DxtBitmapContent
|
||||
{
|
||||
public Dxt1BitmapContent(int width, int height)
|
||||
: base(8, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.Dxt1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "DXT1 " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class Dxt3BitmapContent : DxtBitmapContent
|
||||
{
|
||||
public Dxt3BitmapContent(int width, int height)
|
||||
: base(16, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.Dxt3;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "DXT3 " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class Dxt5BitmapContent : DxtBitmapContent
|
||||
{
|
||||
public Dxt5BitmapContent(int width, int height)
|
||||
: base(16, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.Dxt5;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "DXT5 " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
// 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.Graphics;
|
||||
using Nvidia.TextureTools;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public abstract class DxtBitmapContent : BitmapContent
|
||||
{
|
||||
private byte[] _bitmapData;
|
||||
private int _blockSize;
|
||||
private SurfaceFormat _format;
|
||||
|
||||
private int _nvttWriteOffset;
|
||||
|
||||
protected DxtBitmapContent(int blockSize)
|
||||
{
|
||||
if (!((blockSize == 8) || (blockSize == 16)))
|
||||
throw new ArgumentException("Invalid block size");
|
||||
_blockSize = blockSize;
|
||||
TryGetFormat(out _format);
|
||||
}
|
||||
|
||||
protected DxtBitmapContent(int blockSize, int width, int height)
|
||||
: this(blockSize)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public override byte[] GetPixelData()
|
||||
{
|
||||
return _bitmapData;
|
||||
}
|
||||
|
||||
public override void SetPixelData(byte[] sourceData)
|
||||
{
|
||||
_bitmapData = sourceData;
|
||||
}
|
||||
|
||||
private void NvttBeginImage(int size, int width, int height, int depth, int face, int miplevel)
|
||||
{
|
||||
_bitmapData = new byte[size];
|
||||
_nvttWriteOffset = 0;
|
||||
}
|
||||
|
||||
private bool NvttWriteImage(IntPtr data, int length)
|
||||
{
|
||||
Marshal.Copy(data, _bitmapData, _nvttWriteOffset, length);
|
||||
_nvttWriteOffset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void NvttEndImage()
|
||||
{
|
||||
}
|
||||
|
||||
private static void PrepareNVTT(byte[] data)
|
||||
{
|
||||
for (var x = 0; x < data.Length; x += 4)
|
||||
{
|
||||
// NVTT wants BGRA where our source is RGBA so
|
||||
// we swap the red and blue channels.
|
||||
data[x] ^= data[x + 2];
|
||||
data[x + 2] ^= data[x];
|
||||
data[x] ^= data[x + 2];
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrepareNVTT_DXT1(byte[] data, out bool hasTransparency)
|
||||
{
|
||||
hasTransparency = false;
|
||||
|
||||
for (var x = 0; x < data.Length; x += 4)
|
||||
{
|
||||
// NVTT wants BGRA where our source is RGBA so
|
||||
// we swap the red and blue channels.
|
||||
data[x] ^= data[x + 2];
|
||||
data[x + 2] ^= data[x];
|
||||
data[x] ^= data[x + 2];
|
||||
|
||||
// Look for non-opaque pixels.
|
||||
var alpha = data[x + 3];
|
||||
if (alpha < 255)
|
||||
hasTransparency = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (format == sourceFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
SetPixelData(sourceBitmap.GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Add a XNA unit test to see what it does
|
||||
// my guess is that this is invalid for DXT.
|
||||
//
|
||||
// Destination region copy is not yet supported
|
||||
if (destinationRegion != new Rectangle(0, 0, Width, Height))
|
||||
return false;
|
||||
|
||||
// If the source is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(sourceBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, this, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// NVTT wants 8bit data in BGRA format.
|
||||
var colorBitmap = new PixelBitmapContent<Color>(sourceBitmap.Width, sourceBitmap.Height);
|
||||
BitmapContent.Copy(sourceBitmap, colorBitmap);
|
||||
var sourceData = colorBitmap.GetPixelData();
|
||||
var dataHandle = GCHandle.Alloc(sourceData, GCHandleType.Pinned);
|
||||
|
||||
AlphaMode alphaMode;
|
||||
Format outputFormat;
|
||||
var alphaDither = false;
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.Dxt1:
|
||||
case SurfaceFormat.Dxt1SRgb:
|
||||
{
|
||||
bool hasTransparency;
|
||||
PrepareNVTT_DXT1(sourceData, out hasTransparency);
|
||||
outputFormat = hasTransparency ? Format.DXT1a : Format.DXT1;
|
||||
alphaMode = hasTransparency ? AlphaMode.Transparency : AlphaMode.None;
|
||||
alphaDither = true;
|
||||
break;
|
||||
}
|
||||
case SurfaceFormat.Dxt3:
|
||||
case SurfaceFormat.Dxt3SRgb:
|
||||
{
|
||||
PrepareNVTT(sourceData);
|
||||
outputFormat = Format.DXT3;
|
||||
alphaMode = AlphaMode.Transparency;
|
||||
break;
|
||||
}
|
||||
case SurfaceFormat.Dxt5:
|
||||
case SurfaceFormat.Dxt5SRgb:
|
||||
{
|
||||
PrepareNVTT(sourceData);
|
||||
outputFormat = Format.DXT5;
|
||||
alphaMode = AlphaMode.Transparency;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid DXT surface format!");
|
||||
}
|
||||
|
||||
// Do all the calls to the NVTT wrapper within this handler
|
||||
// so we properly clean up if things blow up.
|
||||
try
|
||||
{
|
||||
var dataPtr = dataHandle.AddrOfPinnedObject();
|
||||
|
||||
var inputOptions = new InputOptions();
|
||||
inputOptions.SetTextureLayout(TextureType.Texture2D, colorBitmap.Width, colorBitmap.Height, 1);
|
||||
inputOptions.SetMipmapData(dataPtr, colorBitmap.Width, colorBitmap.Height, 1, 0, 0);
|
||||
inputOptions.SetMipmapGeneration(false);
|
||||
inputOptions.SetGamma(1.0f, 1.0f);
|
||||
inputOptions.SetAlphaMode(alphaMode);
|
||||
|
||||
var compressionOptions = new CompressionOptions();
|
||||
compressionOptions.SetFormat(outputFormat);
|
||||
compressionOptions.SetQuality(Quality.Normal);
|
||||
|
||||
// TODO: This isn't working which keeps us from getting the
|
||||
// same alpha dither behavior on DXT1 as XNA.
|
||||
//
|
||||
// See https://github.com/MonoGame/MonoGame/issues/6259
|
||||
//
|
||||
//if (alphaDither)
|
||||
//compressionOptions.SetQuantization(false, false, true);
|
||||
|
||||
var outputOptions = new OutputOptions();
|
||||
outputOptions.SetOutputHeader(false);
|
||||
outputOptions.SetOutputOptionsOutputHandler(NvttBeginImage, NvttWriteImage, NvttEndImage);
|
||||
|
||||
var dxtCompressor = new Compressor();
|
||||
dxtCompressor.Compress(inputOptions, compressionOptions, outputOptions);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dataHandle.Free();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
var fullRegion = new Rectangle(0, 0, Width, Height);
|
||||
if ((format == destinationFormat) && (sourceRegion == fullRegion) && (sourceRegion == destinationRegion))
|
||||
{
|
||||
destinationBitmap.SetPixelData(GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// No other support for copying from a DXT texture yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the source code for a DirectX Effect, loaded from a .fx file.
|
||||
/// </summary>
|
||||
public class EffectContent : ContentItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of EffectContent.
|
||||
/// </summary>
|
||||
public EffectContent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the effect program source code.
|
||||
/// </summary>
|
||||
public string EffectCode { get; set; }
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class EffectMaterialContent : MaterialContent
|
||||
{
|
||||
public const string EffectKey = "Effect";
|
||||
public const string CompiledEffectKey = "CompiledEffect";
|
||||
|
||||
public ExternalReference<EffectContent> Effect
|
||||
{
|
||||
get { return GetReferenceTypeProperty<ExternalReference<EffectContent>>(EffectKey); }
|
||||
set { SetProperty(EffectKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<CompiledEffectContent> CompiledEffect
|
||||
{
|
||||
get { return GetReferenceTypeProperty<ExternalReference<CompiledEffectContent>>(CompiledEffectKey); }
|
||||
set { SetProperty(CompiledEffectKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class EnvironmentMapMaterialContent : MaterialContent
|
||||
{
|
||||
public const string AlphaKey = "Alpha";
|
||||
public const string DiffuseColorKey = "DiffuseColor";
|
||||
public const string EmissiveColorKey = "EmissiveColor";
|
||||
public const string EnvironmentMapKey = "EnvironmentMap";
|
||||
public const string EnvironmentMapAmountKey = "EnvironmentMapAmount";
|
||||
public const string EnvironmentMapSpecularKey = " EnvironmentMapSpecular";
|
||||
public const string FresnelFactorKey = "FresnelFactor";
|
||||
public const string TextureKey = "Texture";
|
||||
|
||||
public float? Alpha
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(AlphaKey); }
|
||||
set { SetProperty(AlphaKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? DiffuseColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(DiffuseColorKey); }
|
||||
set { SetProperty(DiffuseColorKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? EmissiveColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(EmissiveColorKey); }
|
||||
set { SetProperty(EmissiveColorKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> EnvironmentMap
|
||||
{
|
||||
get { return GetTexture(EnvironmentMapKey); }
|
||||
set { SetTexture(EnvironmentMapKey, value); }
|
||||
}
|
||||
|
||||
public float? EnvironmentMapAmount
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(EnvironmentMapAmountKey); }
|
||||
set { SetProperty(EnvironmentMapAmountKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? EnvironmentMapSpecular
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(EnvironmentMapSpecularKey); }
|
||||
set { SetProperty(EnvironmentMapSpecularKey, value); }
|
||||
}
|
||||
|
||||
public float? FresnelFactor
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(FresnelFactorKey); }
|
||||
set { SetProperty(FresnelFactorKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture
|
||||
{
|
||||
get { return GetTexture(TextureKey); }
|
||||
set { SetTexture(TextureKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// 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.Graphics;
|
||||
using PVRTexLibNET;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports the processing of a texture compressed using ETC1.
|
||||
/// </summary>
|
||||
public class Etc1BitmapContent : BitmapContent
|
||||
{
|
||||
byte[] _data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of Etc1BitmapContent.
|
||||
/// </summary>
|
||||
protected Etc1BitmapContent()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of Etc1BitmapContent with the specified width or height.
|
||||
/// </summary>
|
||||
/// <param name="width">Width in pixels of the bitmap resource.</param>
|
||||
/// <param name="height">Height in pixels of the bitmap resource.</param>
|
||||
public Etc1BitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
public override byte[] GetPixelData()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
public override void SetPixelData(byte[] sourceData)
|
||||
{
|
||||
int bytesRequired = ((Width + 3) >> 2) * ((Height + 3) >> 2) * SurfaceFormat.RgbEtc1.GetSize();
|
||||
if (bytesRequired != sourceData.Length)
|
||||
throw new ArgumentException("ETC1 bitmap with width " + Width + " and height " + Height + " needs "
|
||||
+ bytesRequired + " bytes. Received " + sourceData.Length + " bytes");
|
||||
|
||||
if (_data == null || _data.Length != bytesRequired)
|
||||
_data = new byte[bytesRequired];
|
||||
Buffer.BlockCopy(sourceData, 0, _data, 0, bytesRequired);
|
||||
}
|
||||
|
||||
protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
return false;
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (SurfaceFormat.RgbEtc1 == sourceFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
SetPixelData(sourceBitmap.GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Destination region copy is not yet supported
|
||||
if (destinationRegion != new Rectangle(0, 0, Width, Height))
|
||||
return false;
|
||||
|
||||
// If the source is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(sourceBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, this, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the texture object in the PVR library
|
||||
var sourceData = sourceBitmap.GetPixelData();
|
||||
var rgba32F = (PixelFormat)0x2020202061626772; // static const PixelType PVRStandard32PixelType = PixelType('r', 'g', 'b', 'a', 32, 32, 32, 32);
|
||||
using (var pvrTexture = PVRTexture.CreateTexture(sourceData, (uint)sourceBitmap.Width, (uint)sourceBitmap.Height, 1,
|
||||
rgba32F, true, VariableType.Float, ColourSpace.lRGB))
|
||||
{
|
||||
// Resize the bitmap if needed
|
||||
if ((sourceBitmap.Width != Width) || (sourceBitmap.Height != Height))
|
||||
pvrTexture.Resize((uint)Width, (uint)Height, 1, ResizeMode.Cubic);
|
||||
pvrTexture.Transcode(PixelFormat.ETC1, VariableType.UnsignedByte, ColourSpace.lRGB /*, CompressorQuality.ETCMediumPerceptual, true*/);
|
||||
var texDataSize = pvrTexture.GetTextureDataSize(0);
|
||||
var texData = new byte[texDataSize];
|
||||
pvrTexture.GetTextureData(texData, texDataSize);
|
||||
SetPixelData(texData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
return false;
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (SurfaceFormat.RgbEtc1 == destinationFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
destinationBitmap.SetPixelData(GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// No other support for copying from a ETC1 texture yet
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbEtc1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "ETC1 " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Assorted helpers for doing useful things with bitmaps.
|
||||
internal static class BitmapUtils
|
||||
{
|
||||
// Checks whether an area of a bitmap contains entirely the specified alpha value.
|
||||
public static bool IsAlphaEntirely(byte expectedAlpha, BitmapContent bitmap, Rectangle? region = null)
|
||||
{
|
||||
var bitmapRegion = region.HasValue ? region.Value : new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
||||
// Works with PixelBitmapContent<byte> at this stage
|
||||
if (bitmap is PixelBitmapContent<byte>)
|
||||
{
|
||||
var bmp = bitmap as PixelBitmapContent<byte>;
|
||||
for (int y = 0; y < bitmapRegion.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bitmapRegion.Width; x++)
|
||||
{
|
||||
var alpha = bmp.GetPixel(bitmapRegion.X + x, bitmapRegion.Y + y);
|
||||
if (alpha != expectedAlpha)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (bitmap is PixelBitmapContent<Color>)
|
||||
{
|
||||
var bmp = bitmap as PixelBitmapContent<Color>;
|
||||
for (int y = 0; y < bitmapRegion.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bitmapRegion.Width; x++)
|
||||
{
|
||||
var alpha = bmp.GetPixel(bitmapRegion.X + x, bitmapRegion.Y + y).A;
|
||||
if (alpha != expectedAlpha)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
throw new ArgumentException("Expected PixelBitmapContent<byte> or PixelBitmapContent<Color>, got " + bitmap.GetType().Name, "bitmap");
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Describes a range of consecutive characters that should be included in the font.
|
||||
[TypeConverter(typeof(CharacterRegionTypeConverter))]
|
||||
public struct CharacterRegion
|
||||
{
|
||||
public char Start;
|
||||
public char End;
|
||||
|
||||
// Enumerates all characters within the region.
|
||||
public IEnumerable<Char> Characters()
|
||||
{
|
||||
for (var c = Start; c <= End; c++)
|
||||
{
|
||||
yield return c;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor.
|
||||
public CharacterRegion(char start, char end)
|
||||
{
|
||||
if (start > end)
|
||||
throw new ArgumentException();
|
||||
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
// Default to just the base ASCII character set.
|
||||
public static CharacterRegion Default = new CharacterRegion(' ', '~');
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test if there is an element in this enumeration.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the element</typeparam>
|
||||
/// <param name="source">The enumerable source.</param>
|
||||
/// <returns><c>true</c> if there is an element in this enumeration, <c>false</c> otherwise</returns>
|
||||
public static bool Any<T>(IEnumerable<T> source)
|
||||
{
|
||||
return source.GetEnumerator().MoveNext();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Select elements from an enumeration.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the T source.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the T result.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="selector">The selector.</param>
|
||||
/// <returns>A enumeration of selected values</returns>
|
||||
public static IEnumerable<TResult> SelectMany<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
|
||||
{
|
||||
foreach (TSource sourceItem in source)
|
||||
{
|
||||
foreach (TResult result in selector(sourceItem))
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects distinct elements from an enumeration.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the T source.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="comparer">The comparer.</param>
|
||||
/// <returns>A enumeration of selected values</returns>
|
||||
public static IEnumerable<TSource> Distinct<TSource>(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer = null)
|
||||
{
|
||||
if (comparer == null)
|
||||
comparer = EqualityComparer<TSource>.Default;
|
||||
|
||||
// using Dictionary is not really efficient but easy to implement
|
||||
var values = new Dictionary<TSource, object>(comparer);
|
||||
foreach (TSource sourceItem in source)
|
||||
{
|
||||
if (!values.ContainsKey(sourceItem))
|
||||
{
|
||||
values.Add(sourceItem, null);
|
||||
yield return sourceItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class CharacterRegionTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string);
|
||||
}
|
||||
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
// Input must be a string.
|
||||
string source = value as string;
|
||||
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
// Supported input formats:
|
||||
// A
|
||||
// A-Z
|
||||
// 32-127
|
||||
// 0x20-0x7F
|
||||
|
||||
var splitStr = source.Split('-');
|
||||
var split = new char[splitStr.Length];
|
||||
for (int i = 0; i < splitStr.Length; i++)
|
||||
{
|
||||
split[i] = ConvertCharacter(splitStr[i]);
|
||||
}
|
||||
|
||||
switch (split.Length)
|
||||
{
|
||||
case 1:
|
||||
// Only a single character (eg. "a").
|
||||
return new CharacterRegion(split[0], split[0]);
|
||||
|
||||
case 2:
|
||||
// Range of characters (eg. "a-z").
|
||||
return new CharacterRegion(split[0], split[1]);
|
||||
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static char ConvertCharacter(string value)
|
||||
{
|
||||
if (value.Length == 1)
|
||||
{
|
||||
// Single character directly specifies a codepoint.
|
||||
return value[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise it must be an integer (eg. "32" or "0x20").
|
||||
return (char)(int)intConverter.ConvertFromInvariantString(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ABCFloat
|
||||
{
|
||||
public float A;
|
||||
public float B;
|
||||
public float C;
|
||||
}
|
||||
|
||||
// Represents a single character within a font.
|
||||
internal class Glyph
|
||||
{
|
||||
// Constructor.
|
||||
public Glyph(char character, BitmapContent bitmap, Rectangle? subrect = null)
|
||||
{
|
||||
this.Character = character;
|
||||
this.Bitmap = bitmap;
|
||||
this.Subrect = subrect.GetValueOrDefault(new Rectangle(0, 0, bitmap.Width, bitmap.Height));
|
||||
this.Width = bitmap.Width;
|
||||
this.Height = bitmap.Height;
|
||||
}
|
||||
|
||||
// Unicode codepoint.
|
||||
public char Character;
|
||||
|
||||
// Glyph image data (may only use a portion of a larger bitmap).
|
||||
public BitmapContent Bitmap;
|
||||
public Rectangle Subrect;
|
||||
|
||||
// Layout information.
|
||||
public float XOffset;
|
||||
public float YOffset;
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
public float XAdvance;
|
||||
|
||||
public ABCFloat CharacterWidths;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Crops unused space from around the edge of a glyph bitmap.
|
||||
internal static class GlyphCropper
|
||||
{
|
||||
public static void Crop(Glyph glyph)
|
||||
{
|
||||
// Crop the top.
|
||||
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, glyph.Subrect.Width, 1)))
|
||||
{
|
||||
glyph.Subrect.Y++;
|
||||
glyph.Subrect.Height--;
|
||||
|
||||
glyph.YOffset++;
|
||||
}
|
||||
|
||||
// Crop the bottom.
|
||||
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Bottom - 1, glyph.Subrect.Width, 1)))
|
||||
{
|
||||
glyph.Subrect.Height--;
|
||||
}
|
||||
|
||||
// Crop the left.
|
||||
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
|
||||
{
|
||||
glyph.Subrect.X++;
|
||||
glyph.Subrect.Width--;
|
||||
|
||||
glyph.XOffset++;
|
||||
}
|
||||
|
||||
// Crop the right.
|
||||
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.Right - 1, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
|
||||
{
|
||||
glyph.Subrect.Width--;
|
||||
|
||||
glyph.XAdvance++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Helper for arranging many small bitmaps onto a single larger surface.
|
||||
internal static class GlyphPacker
|
||||
{
|
||||
public static BitmapContent ArrangeGlyphs(Glyph[] sourceGlyphs, bool requirePOT, bool requireSquare)
|
||||
{
|
||||
// Build up a list of all the glyphs needing to be arranged.
|
||||
var glyphs = new List<ArrangedGlyph>();
|
||||
|
||||
for (int i = 0; i < sourceGlyphs.Length; i++)
|
||||
{
|
||||
var glyph = new ArrangedGlyph();
|
||||
|
||||
glyph.Source = sourceGlyphs[i];
|
||||
|
||||
// Leave a one pixel border around every glyph in the output bitmap.
|
||||
glyph.Width = sourceGlyphs[i].Subrect.Width + 2;
|
||||
glyph.Height = sourceGlyphs[i].Subrect.Height + 2;
|
||||
|
||||
glyphs.Add(glyph);
|
||||
}
|
||||
|
||||
// Sort so the largest glyphs get arranged first.
|
||||
glyphs.Sort(CompareGlyphSizes);
|
||||
|
||||
// Work out how big the output bitmap should be.
|
||||
int outputWidth = GuessOutputWidth(sourceGlyphs);
|
||||
int outputHeight = 0;
|
||||
|
||||
// Choose positions for each glyph, one at a time.
|
||||
for (int i = 0; i < glyphs.Count; i++)
|
||||
{
|
||||
PositionGlyph(glyphs, i, outputWidth);
|
||||
|
||||
outputHeight = Math.Max(outputHeight, glyphs[i].Y + glyphs[i].Height);
|
||||
}
|
||||
|
||||
// Create the merged output bitmap.
|
||||
outputHeight = MakeValidTextureSize(outputHeight, requirePOT);
|
||||
|
||||
if (requireSquare)
|
||||
{
|
||||
outputHeight = Math.Max (outputWidth, outputHeight);
|
||||
outputWidth = outputHeight;
|
||||
}
|
||||
|
||||
return CopyGlyphsToOutput(glyphs, outputWidth, outputHeight);
|
||||
}
|
||||
|
||||
// Once arranging is complete, copies each glyph to its chosen position in the single larger output bitmap.
|
||||
static BitmapContent CopyGlyphsToOutput(List<ArrangedGlyph> glyphs, int width, int height)
|
||||
{
|
||||
var output = new PixelBitmapContent<Color>(width, height);
|
||||
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
var sourceGlyph = glyph.Source;
|
||||
var sourceRegion = sourceGlyph.Subrect;
|
||||
var destinationRegion = new Rectangle(glyph.X + 1, glyph.Y + 1, sourceRegion.Width, sourceRegion.Height);
|
||||
|
||||
BitmapContent.Copy(sourceGlyph.Bitmap, sourceRegion, output, destinationRegion);
|
||||
|
||||
sourceGlyph.Bitmap = output;
|
||||
sourceGlyph.Subrect = destinationRegion;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
// Internal helper class keeps track of a glyph while it is being arranged.
|
||||
class ArrangedGlyph
|
||||
{
|
||||
public Glyph Source;
|
||||
|
||||
public int X;
|
||||
public int Y;
|
||||
|
||||
public int Width;
|
||||
public int Height;
|
||||
}
|
||||
|
||||
|
||||
// Works out where to position a single glyph.
|
||||
static void PositionGlyph(List<ArrangedGlyph> glyphs, int index, int outputWidth)
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Is this position free for us to use?
|
||||
int intersects = FindIntersectingGlyph(glyphs, index, x, y);
|
||||
|
||||
if (intersects < 0)
|
||||
{
|
||||
glyphs[index].X = x;
|
||||
glyphs[index].Y = y;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip past the existing glyph that we collided with.
|
||||
x = glyphs[intersects].X + glyphs[intersects].Width;
|
||||
|
||||
// If we ran out of room to move to the right, try the next line down instead.
|
||||
if (x + glyphs[index].Width > outputWidth)
|
||||
{
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks if a proposed glyph position collides with anything that we already arranged.
|
||||
static int FindIntersectingGlyph(List<ArrangedGlyph> glyphs, int index, int x, int y)
|
||||
{
|
||||
int w = glyphs[index].Width;
|
||||
int h = glyphs[index].Height;
|
||||
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
if (glyphs[i].X >= x + w)
|
||||
continue;
|
||||
|
||||
if (glyphs[i].X + glyphs[i].Width <= x)
|
||||
continue;
|
||||
|
||||
if (glyphs[i].Y >= y + h)
|
||||
continue;
|
||||
|
||||
if (glyphs[i].Y + glyphs[i].Height <= y)
|
||||
continue;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Comparison function for sorting glyphs by size.
|
||||
static int CompareGlyphSizes(ArrangedGlyph a, ArrangedGlyph b)
|
||||
{
|
||||
const int heightWeight = 1024;
|
||||
|
||||
int aSize = a.Height * heightWeight + a.Width;
|
||||
int bSize = b.Height * heightWeight + b.Width;
|
||||
|
||||
if (aSize != bSize)
|
||||
return bSize.CompareTo(aSize);
|
||||
else
|
||||
return a.Source.Character.CompareTo(b.Source.Character);
|
||||
}
|
||||
|
||||
|
||||
// Heuristic guesses what might be a good output width for a list of glyphs.
|
||||
static int GuessOutputWidth(Glyph[] sourceGlyphs)
|
||||
{
|
||||
int maxWidth = 0;
|
||||
int totalSize = 0;
|
||||
|
||||
foreach (var glyph in sourceGlyphs)
|
||||
{
|
||||
maxWidth = Math.Max(maxWidth, glyph.Bitmap.Width);
|
||||
totalSize += glyph.Bitmap.Width * glyph.Bitmap.Height;
|
||||
}
|
||||
|
||||
int width = Math.Max((int)Math.Sqrt(totalSize), maxWidth);
|
||||
|
||||
return MakeValidTextureSize(width, true);
|
||||
}
|
||||
|
||||
|
||||
// Rounds a value up to the next larger valid texture size.
|
||||
static int MakeValidTextureSize(int value, bool requirePowerOfTwo)
|
||||
{
|
||||
// In case we want to compress the texture, make sure the size is a multiple of 4.
|
||||
const int blockSize = 4;
|
||||
|
||||
if (requirePowerOfTwo)
|
||||
{
|
||||
// Round up to a power of two.
|
||||
int powerOfTwo = blockSize;
|
||||
|
||||
while (powerOfTwo < value)
|
||||
powerOfTwo <<= 1;
|
||||
|
||||
return powerOfTwo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Round up to the specified block size.
|
||||
return (value + blockSize - 1) & ~(blockSize - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Importer interface allows the conversion tool to support multiple source font formats.
|
||||
internal interface IFontImporter
|
||||
{
|
||||
void Import(FontDescription options, string fontName);
|
||||
|
||||
IEnumerable<Glyph> Glyphs { get; }
|
||||
|
||||
float LineSpacing { get; }
|
||||
|
||||
int YOffsetMin { get; }
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using SharpFont;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
// Uses FreeType to rasterize TrueType fonts into a series of glyph bitmaps.
|
||||
internal class SharpFontImporter : IFontImporter
|
||||
{
|
||||
// Properties hold the imported font data.
|
||||
public IEnumerable<Glyph> Glyphs { get; private set; }
|
||||
|
||||
public float LineSpacing { get; private set; }
|
||||
|
||||
public int YOffsetMin { get; private set; }
|
||||
|
||||
// Size of the temp surface used for GDI+ rasterization.
|
||||
const int MaxGlyphSize = 1024;
|
||||
|
||||
Library lib = null;
|
||||
|
||||
public void Import(FontDescription options, string fontName)
|
||||
{
|
||||
lib = new Library();
|
||||
// Create a bunch of GDI+ objects.
|
||||
var face = CreateFontFace(options, fontName);
|
||||
try
|
||||
{
|
||||
// Which characters do we want to include?
|
||||
var characters = options.Characters;
|
||||
|
||||
var glyphList = new List<Glyph>();
|
||||
// Rasterize each character in turn.
|
||||
foreach (char character in characters)
|
||||
{
|
||||
var glyph = ImportGlyph(character, face);
|
||||
glyphList.Add(glyph);
|
||||
}
|
||||
Glyphs = glyphList;
|
||||
|
||||
// Store the font height.
|
||||
LineSpacing = face.Size.Metrics.Height >> 6;
|
||||
|
||||
// The height used to calculate the Y offset for each character.
|
||||
YOffsetMin = -face.Size.Metrics.Ascender >> 6;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (face != null)
|
||||
face.Dispose();
|
||||
if (lib != null)
|
||||
{
|
||||
lib.Dispose();
|
||||
lib = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Attempts to instantiate the requested GDI+ font object.
|
||||
private Face CreateFontFace(FontDescription options, string fontName)
|
||||
{
|
||||
try
|
||||
{
|
||||
const uint dpi = 96;
|
||||
var face = lib.NewFace(fontName, 0);
|
||||
var fixedSize = ((int)options.Size) << 6;
|
||||
face.SetCharSize(0, fixedSize, dpi, dpi);
|
||||
|
||||
if (face.FamilyName == "Microsoft Sans Serif" && options.FontName != "Microsoft Sans Serif")
|
||||
throw new PipelineException(string.Format("Font {0} is not installed on this computer.", options.FontName));
|
||||
|
||||
return face;
|
||||
|
||||
// A font substitution must have occurred.
|
||||
//throw new Exception(string.Format("Can't find font '{0}'.", options.FontName));
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Rasterizes a single character glyph.
|
||||
private Glyph ImportGlyph(char character, Face face)
|
||||
{
|
||||
uint glyphIndex = face.GetCharIndex(character);
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
// Render the character.
|
||||
BitmapContent glyphBitmap = null;
|
||||
if (face.Glyph.Bitmap.Width > 0 && face.Glyph.Bitmap.Rows > 0)
|
||||
{
|
||||
glyphBitmap = new PixelBitmapContent<byte>(face.Glyph.Bitmap.Width, face.Glyph.Bitmap.Rows);
|
||||
byte[] gpixelAlphas = new byte[face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows];
|
||||
//if the character bitmap has 1bpp we have to expand the buffer data to get the 8bpp pixel data
|
||||
//each byte in bitmap.bufferdata contains the value of to 8 pixels in the row
|
||||
//if bitmap is of width 10, each row has 2 bytes with 10 valid bits, and the last 6 bits of 2nd byte must be discarded
|
||||
if(face.Glyph.Bitmap.PixelMode == PixelMode.Mono)
|
||||
{
|
||||
//variables needed for the expansion, amount of written data, length of the data to write
|
||||
int written = 0, length = face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows;
|
||||
for(int i = 0; written < length; i++)
|
||||
{
|
||||
//width in pixels of each row
|
||||
int width = face.Glyph.Bitmap.Width;
|
||||
while(width > 0)
|
||||
{
|
||||
//valid data in the current byte
|
||||
int stride = MathHelper.Min(8, width);
|
||||
//copy the valid bytes to pixeldata
|
||||
//System.Array.Copy(ExpandByte(face.Glyph.Bitmap.BufferData[i]), 0, gpixelAlphas, written, stride);
|
||||
ExpandByteAndCopy(face.Glyph.Bitmap.BufferData[i], stride, gpixelAlphas, written);
|
||||
written += stride;
|
||||
width -= stride;
|
||||
if(width > 0)
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Marshal.Copy(face.Glyph.Bitmap.Buffer, gpixelAlphas, 0, gpixelAlphas.Length);
|
||||
glyphBitmap.SetPixelData(gpixelAlphas);
|
||||
}
|
||||
|
||||
if (glyphBitmap == null)
|
||||
{
|
||||
var gHA = face.Glyph.Metrics.HorizontalAdvance >> 6;
|
||||
var gVA = face.Size.Metrics.Height >> 6;
|
||||
|
||||
gHA = gHA > 0 ? gHA : gVA;
|
||||
gVA = gVA > 0 ? gVA : gHA;
|
||||
|
||||
glyphBitmap = new PixelBitmapContent<byte>(gHA, gVA);
|
||||
}
|
||||
|
||||
// not sure about this at all
|
||||
var abc = new ABCFloat ();
|
||||
abc.A = face.Glyph.Metrics.HorizontalBearingX >> 6;
|
||||
abc.B = face.Glyph.Metrics.Width >> 6;
|
||||
abc.C = (face.Glyph.Metrics.HorizontalAdvance >> 6) - (abc.A + abc.B);
|
||||
|
||||
// Construct the output Glyph object.
|
||||
return new Glyph(character, glyphBitmap)
|
||||
{
|
||||
XOffset = -(face.Glyph.Advance.X >> 6),
|
||||
XAdvance = face.Glyph.Metrics.HorizontalAdvance >> 6,
|
||||
YOffset = -(face.Glyph.Metrics.HorizontalBearingY >> 6),
|
||||
CharacterWidths = abc
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads each individual bit of a byte from left to right and expands it to a full byte,
|
||||
/// ones get byte.maxvalue, and zeros get byte.minvalue.
|
||||
/// </summary>
|
||||
/// <param name="origin">Byte to expand and copy</param>
|
||||
/// <param name="length">Number of Bits of the Byte to copy, from 1 to 8</param>
|
||||
/// <param name="destination">Byte array where to copy the results</param>
|
||||
/// <param name="startIndex">Position where to begin copying the results in destination</param>
|
||||
private static void ExpandByteAndCopy(byte origin, int length, byte[] destination, int startIndex)
|
||||
{
|
||||
byte tmp;
|
||||
for(int i = 7; i > 7 - length; i--)
|
||||
{
|
||||
tmp = (byte) (1 << i);
|
||||
if(origin / tmp == 1)
|
||||
{
|
||||
destination[startIndex + 7 - i] = byte.MaxValue;
|
||||
origin -= tmp;
|
||||
}
|
||||
else
|
||||
destination[startIndex + 7 - i] = byte.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
internal class CharacterCollection : ICollection<char>
|
||||
{
|
||||
private List<char> _items;
|
||||
|
||||
public CharacterCollection()
|
||||
{
|
||||
_items = new List<char>();
|
||||
}
|
||||
|
||||
public CharacterCollection(IEnumerable<char> characters)
|
||||
{
|
||||
_items = new List<char>();
|
||||
foreach (var c in characters)
|
||||
Add(c);
|
||||
}
|
||||
|
||||
#region ICollection<char> Members
|
||||
|
||||
public void Add(char item)
|
||||
{
|
||||
if (!_items.Contains(item))
|
||||
_items.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(char item)
|
||||
{
|
||||
return _items.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(char[] array, int arrayIndex)
|
||||
{
|
||||
_items.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _items.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Remove(char item)
|
||||
{
|
||||
return _items.Remove(item);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<char> Members
|
||||
|
||||
public IEnumerator<char> GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides information to the FontDescriptionProcessor describing which font to rasterize, which font size to utilize, and which Unicode characters to include in the processor output.
|
||||
/// </summary>
|
||||
public class FontDescription : ContentItem
|
||||
{
|
||||
private char? defaultCharacter;
|
||||
private string fontName;
|
||||
private float size;
|
||||
private float spacing;
|
||||
private FontDescriptionStyle style;
|
||||
private bool useKerning;
|
||||
private CharacterCollection characters = new CharacterCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the font, such as "Times New Roman" or "Arial". This value cannot be null or empty.
|
||||
/// </summary>
|
||||
[ContentSerializer(AllowNull = false)]
|
||||
public string FontName
|
||||
{
|
||||
get
|
||||
{
|
||||
return fontName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new ArgumentNullException("FontName is null or an empty string.");
|
||||
fontName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size, in points, of the font.
|
||||
/// </summary>
|
||||
public float Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return size;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value <= 0.0f)
|
||||
throw new ArgumentOutOfRangeException("Size must be greater than zero.");
|
||||
size = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of space, in pixels, to insert between letters in a string.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public float Spacing
|
||||
{
|
||||
get
|
||||
{
|
||||
return spacing;
|
||||
}
|
||||
set
|
||||
{
|
||||
spacing = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if kerning information is used when drawing characters.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public bool UseKerning
|
||||
{
|
||||
get
|
||||
{
|
||||
return useKerning;
|
||||
}
|
||||
set
|
||||
{
|
||||
useKerning = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style of the font, expressed as a combination of one or more FontDescriptionStyle flags.
|
||||
/// </summary>
|
||||
public FontDescriptionStyle Style
|
||||
{
|
||||
get
|
||||
{
|
||||
return style;
|
||||
}
|
||||
set
|
||||
{
|
||||
style = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default character for the font.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true)]
|
||||
public Nullable<char> DefaultCharacter
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultCharacter;
|
||||
}
|
||||
set
|
||||
{
|
||||
defaultCharacter = value;
|
||||
}
|
||||
}
|
||||
|
||||
[ContentSerializer(CollectionItemName = "CharacterRegion")]
|
||||
internal CharacterRegion[] CharacterRegions
|
||||
{
|
||||
get
|
||||
{
|
||||
var regions = new List<CharacterRegion>();
|
||||
var chars = Characters.ToList();
|
||||
chars.Sort();
|
||||
|
||||
var start = chars[0];
|
||||
var end = chars[0];
|
||||
|
||||
for (var i=1; i < chars.Count; i++)
|
||||
{
|
||||
if (chars[i] != (end+1))
|
||||
{
|
||||
regions.Add(new CharacterRegion(start, end));
|
||||
start = chars[i];
|
||||
}
|
||||
end = chars[i];
|
||||
}
|
||||
|
||||
regions.Add(new CharacterRegion(start, end));
|
||||
|
||||
return regions.ToArray();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
CharacterRegion characterRegion = value[index];
|
||||
if (characterRegion.End < characterRegion.Start)
|
||||
throw new ArgumentException("CharacterRegion.End must be greater than CharacterRegion.Start");
|
||||
|
||||
for (var start = characterRegion.Start; start <= characterRegion.End; start++)
|
||||
Characters.Add(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ContentSerializerIgnore]
|
||||
public ICollection<char> Characters
|
||||
{
|
||||
get { return characters; }
|
||||
internal set { characters = new CharacterCollection(value); }
|
||||
}
|
||||
|
||||
internal FontDescription()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of FontDescription and initializes its members to the specified font, size, and spacing, using FontDescriptionStyle.Regular as the default value for Style.
|
||||
/// </summary>
|
||||
/// <param name="fontName">The name of the font, such as Times New Roman.</param>
|
||||
/// <param name="size">The size, in points, of the font.</param>
|
||||
/// <param name="spacing">The amount of space, in pixels, to insert between letters in a string.</param>
|
||||
public FontDescription(string fontName, float size, float spacing)
|
||||
: this(fontName, size, spacing, FontDescriptionStyle.Regular, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of FontDescription and initializes its members to the specified font, size, spacing, and style.
|
||||
/// </summary>
|
||||
/// <param name="fontName">The name of the font, such as Times New Roman.</param>
|
||||
/// <param name="size">The size, in points, of the font.</param>
|
||||
/// <param name="spacing">The amount of space, in pixels, to insert between letters in a string.</param>
|
||||
/// <param name="fontStyle">The font style for the font.</param>
|
||||
public FontDescription(string fontName, float size, float spacing, FontDescriptionStyle fontStyle)
|
||||
: this(fontName, size, spacing, fontStyle, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of FontDescription using the specified values.
|
||||
/// </summary>
|
||||
/// <param name="fontName">The name of the font, such as Times New Roman.</param>
|
||||
/// <param name="size">The size, in points, of the font.</param>
|
||||
/// <param name="spacing">The amount of space, in pixels, to insert between letters in a string.</param>
|
||||
/// <param name="fontStyle">The font style for the font.</param>
|
||||
/// <param name="useKerning">true if kerning information is used when drawing characters; false otherwise.</param>
|
||||
public FontDescription(string fontName, float size, float spacing, FontDescriptionStyle fontStyle, bool useKerning)
|
||||
{
|
||||
// Write to the properties so the validation is run
|
||||
FontName = fontName;
|
||||
Size = size;
|
||||
Spacing = spacing;
|
||||
Style = fontStyle;
|
||||
UseKerning = useKerning;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// 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.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags that describe style information to be applied to text.
|
||||
/// You can combine these flags by using a bitwise OR operator (|).
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FontDescriptionStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Bold text.
|
||||
/// </summary>
|
||||
Bold,
|
||||
|
||||
/// <summary>
|
||||
/// Italic text.
|
||||
/// </summary>
|
||||
Italic,
|
||||
|
||||
/// <summary>
|
||||
/// Normal text.
|
||||
/// </summary>
|
||||
Regular,
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties that define various aspects of a geometry batch.
|
||||
/// </summary>
|
||||
public class GeometryContent : ContentItem
|
||||
{
|
||||
IndexCollection indices;
|
||||
MaterialContent material;
|
||||
MeshContent parent;
|
||||
VertexContent vertices;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of triangle indices for this geometry batch. Geometry is stored as an indexed triangle list, where each group of three indices defines a single triangle.
|
||||
/// </summary>
|
||||
public IndexCollection Indices
|
||||
{
|
||||
get
|
||||
{
|
||||
return indices;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the material of the parent mesh.
|
||||
/// </summary>
|
||||
public MaterialContent Material
|
||||
{
|
||||
get
|
||||
{
|
||||
return material;
|
||||
}
|
||||
set
|
||||
{
|
||||
material = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent MeshContent for this object.
|
||||
/// </summary>
|
||||
public MeshContent Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
set
|
||||
{
|
||||
parent = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of vertex batches for the geometry batch.
|
||||
/// </summary>
|
||||
public VertexContent Vertices
|
||||
{
|
||||
get
|
||||
{
|
||||
return vertices;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of GeometryContent.
|
||||
/// </summary>
|
||||
public GeometryContent()
|
||||
{
|
||||
indices = new IndexCollection();
|
||||
vertices = new VertexContent(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -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.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for maintaining a collection of geometry batches that make up a mesh.
|
||||
/// </summary>
|
||||
public sealed class GeometryContentCollection : ChildCollection<MeshContent, GeometryContent>
|
||||
{
|
||||
internal GeometryContentCollection(MeshContent parent)
|
||||
: base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of a child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being retrieved.</param>
|
||||
/// <returns>The parent of the child object.</returns>
|
||||
protected override MeshContent GetParent(GeometryContent child)
|
||||
{
|
||||
return child.Parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the parent of the specified child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being set.</param>
|
||||
/// <param name="parent">The parent of the child object.</param>
|
||||
protected override void SetParent(GeometryContent child, MeshContent parent)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
// 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 Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
||||
using FreeImageAPI;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public static class GraphicsUtil
|
||||
{
|
||||
internal static BitmapContent Resize(this BitmapContent bitmap, int newWidth, int newHeight)
|
||||
{
|
||||
BitmapContent src = bitmap;
|
||||
SurfaceFormat format;
|
||||
src.TryGetFormat(out format);
|
||||
if (format != SurfaceFormat.Vector4)
|
||||
{
|
||||
var v4 = new PixelBitmapContent<Vector4>(src.Width, src.Height);
|
||||
BitmapContent.Copy(src, v4);
|
||||
src = v4;
|
||||
}
|
||||
|
||||
// Convert to FreeImage bitmap
|
||||
var bytes = src.GetPixelData();
|
||||
var fi = FreeImage.ConvertFromRawBits(bytes, FREE_IMAGE_TYPE.FIT_RGBAF, src.Width, src.Height, SurfaceFormat.Vector4.GetSize() * src.Width, 128, 0, 0, 0, true);
|
||||
|
||||
// Resize
|
||||
var newfi = FreeImage.Rescale(fi, newWidth, newHeight, FREE_IMAGE_FILTER.FILTER_BICUBIC);
|
||||
FreeImage.UnloadEx(ref fi);
|
||||
|
||||
// Convert back to PixelBitmapContent<Vector4>
|
||||
src = new PixelBitmapContent<Vector4>(newWidth, newHeight);
|
||||
bytes = new byte[SurfaceFormat.Vector4.GetSize() * newWidth * newHeight];
|
||||
FreeImage.ConvertToRawBits(bytes, newfi, SurfaceFormat.Vector4.GetSize() * newWidth, 128, 0, 0, 0, true);
|
||||
src.SetPixelData(bytes);
|
||||
FreeImage.UnloadEx(ref newfi);
|
||||
// Convert back to source type if required
|
||||
if (format != SurfaceFormat.Vector4)
|
||||
{
|
||||
var s = (BitmapContent)Activator.CreateInstance(bitmap.GetType(), new object[] { newWidth, newHeight });
|
||||
BitmapContent.Copy(src, s);
|
||||
src = s;
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
public static void BGRAtoRGBA(byte[] data)
|
||||
{
|
||||
for (var x = 0; x < data.Length; x += 4)
|
||||
{
|
||||
data[x] ^= data[x + 2];
|
||||
data[x + 2] ^= data[x];
|
||||
data[x] ^= data[x + 2];
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPowerOfTwo(int x)
|
||||
{
|
||||
return (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next power of two. Returns same value if already is PoT.
|
||||
/// </summary>
|
||||
public static int GetNextPowerOfTwo(int value)
|
||||
{
|
||||
if (IsPowerOfTwo(value))
|
||||
return value;
|
||||
|
||||
var nearestPower = 1;
|
||||
while (nearestPower < value)
|
||||
nearestPower = nearestPower << 1;
|
||||
|
||||
return nearestPower;
|
||||
}
|
||||
|
||||
enum AlphaRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Pixel data has no alpha values below 1.0.
|
||||
/// </summary>
|
||||
Opaque,
|
||||
|
||||
/// <summary>
|
||||
/// Pixel data contains alpha values that are either 0.0 or 1.0.
|
||||
/// </summary>
|
||||
Cutout,
|
||||
|
||||
/// <summary>
|
||||
/// Pixel data contains alpha values that cover the full range of 0.0 to 1.0.
|
||||
/// </summary>
|
||||
Full,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alpha range in a set of pixels.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">A bitmap of full-colour floating point pixel data in RGBA or BGRA order.</param>
|
||||
/// <returns>A member of the AlphaRange enum to describe the range of alpha in the pixel data.</returns>
|
||||
static AlphaRange CalculateAlphaRange(BitmapContent bitmap)
|
||||
{
|
||||
AlphaRange result = AlphaRange.Opaque;
|
||||
var pixelBitmap = bitmap as PixelBitmapContent<Vector4>;
|
||||
if (pixelBitmap != null)
|
||||
{
|
||||
for (int y = 0; y < pixelBitmap.Height; ++y)
|
||||
{
|
||||
var row = pixelBitmap.GetRow(y);
|
||||
foreach (var pixel in row)
|
||||
{
|
||||
if (pixel.W == 0.0)
|
||||
result = AlphaRange.Cutout;
|
||||
else if (pixel.W < 1.0)
|
||||
return AlphaRange.Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CompressPvrtc(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate number of mip levels
|
||||
var width = content.Faces[0][0].Height;
|
||||
var height = content.Faces[0][0].Width;
|
||||
|
||||
if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height) || (width != height))
|
||||
{
|
||||
context.Logger.LogWarning(null, content.Identity, "PVR compression requires width and height to be powers of two and equal. Falling back to 16-bit color.");
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PvrtcRgb4BitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(PvrtcRgba4BitmapContent));
|
||||
}
|
||||
|
||||
public static void CompressDxt(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
var face = content.Faces[0][0];
|
||||
|
||||
if (context.TargetProfile == GraphicsProfile.Reach)
|
||||
{
|
||||
if (!IsPowerOfTwo(face.Width) || !IsPowerOfTwo(face.Height))
|
||||
throw new PipelineException("DXT compression requires width and height must be powers of two in Reach graphics profile.");
|
||||
}
|
||||
|
||||
// Test the alpha channel to figure out if we have alpha.
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
// TODO: This isn't quite right.
|
||||
//
|
||||
// We should be generating DXT1 textures for cutout alpha
|
||||
// as DXT1 supports 1bit alpha and it uses less memory.
|
||||
//
|
||||
// XNA never generated DXT3 for textures... it always picked
|
||||
// between DXT1 for cutouts and DXT5 for fractional alpha.
|
||||
//
|
||||
// DXT3 however can produce better results for high frequency
|
||||
// alpha like a chain link fence where is DXT5 is better for
|
||||
// low frequency alpha like clouds. I don't know how we can
|
||||
// pick the right thing in this case without a hint.
|
||||
//
|
||||
if (isSpriteFont)
|
||||
CompressFontDXT3(content);
|
||||
else if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(Dxt1BitmapContent));
|
||||
else if (alphaRange == AlphaRange.Cutout)
|
||||
content.ConvertBitmapType(typeof(Dxt3BitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(Dxt5BitmapContent));
|
||||
}
|
||||
|
||||
static public void CompressAti(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Full)
|
||||
content.ConvertBitmapType(typeof(AtcExplicitBitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(AtcInterpolatedBitmapContent));
|
||||
}
|
||||
|
||||
static public void CompressEtc1(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
// Use BGRA4444 for textures with non-opaque alpha values
|
||||
if (alphaRange != AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra4444>));
|
||||
else
|
||||
{
|
||||
// PVR SGX does not handle non-POT ETC1 textures.
|
||||
// https://code.google.com/p/libgdx/issues/detail?id=1310
|
||||
// Since we already enforce POT for PVR and DXT in Reach, we will also enforce POT for ETC1
|
||||
if (!IsPowerOfTwo(face.Width) || !IsPowerOfTwo(face.Height))
|
||||
{
|
||||
context.Logger.LogWarning(null, content.Identity, "ETC1 compression requires width and height to be powers of two due to hardware restrictions on some devices. Falling back to BGR565.");
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgr565>));
|
||||
}
|
||||
else
|
||||
content.ConvertBitmapType(typeof(Etc1BitmapContent));
|
||||
}
|
||||
}
|
||||
|
||||
static public void CompressColor16Bit(ContentProcessorContext context, TextureContent content)
|
||||
{
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgr565>));
|
||||
else if (alphaRange == AlphaRange.Cutout)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra5551>));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra4444>));
|
||||
}
|
||||
|
||||
// Compress the greyscale font texture page using a specially-formulated DXT3 mode
|
||||
static public unsafe void CompressFontDXT3(TextureContent content)
|
||||
{
|
||||
if (content.Faces.Count > 1)
|
||||
throw new PipelineException("Font textures should only have one face");
|
||||
|
||||
var block = new Vector4[16];
|
||||
for (int i = 0; i < content.Faces[0].Count; ++i)
|
||||
{
|
||||
var face = content.Faces[0][i];
|
||||
var xBlocks = (face.Width + 3) / 4;
|
||||
var yBlocks = (face.Height + 3) / 4;
|
||||
var dxt3Size = xBlocks * yBlocks * 16;
|
||||
var buffer = new byte[dxt3Size];
|
||||
|
||||
var bytes = face.GetPixelData();
|
||||
fixed (byte* b = bytes)
|
||||
{
|
||||
Vector4* colors = (Vector4*)b;
|
||||
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
while (h < (face.Height & ~3))
|
||||
{
|
||||
w = 0;
|
||||
x = 0;
|
||||
|
||||
var h0 = h * face.Width;
|
||||
var h1 = h0 + face.Width;
|
||||
var h2 = h1 + face.Width;
|
||||
var h3 = h2 + face.Width;
|
||||
|
||||
while (w < (face.Width & ~3))
|
||||
{
|
||||
block[0] = colors[w + h0];
|
||||
block[1] = colors[w + h0 + 1];
|
||||
block[2] = colors[w + h0 + 2];
|
||||
block[3] = colors[w + h0 + 3];
|
||||
block[4] = colors[w + h1];
|
||||
block[5] = colors[w + h1 + 1];
|
||||
block[6] = colors[w + h1 + 2];
|
||||
block[7] = colors[w + h1 + 3];
|
||||
block[8] = colors[w + h2];
|
||||
block[9] = colors[w + h2 + 1];
|
||||
block[10] = colors[w + h2 + 2];
|
||||
block[11] = colors[w + h2 + 3];
|
||||
block[12] = colors[w + h3];
|
||||
block[13] = colors[w + h3 + 1];
|
||||
block[14] = colors[w + h3 + 2];
|
||||
block[15] = colors[w + h3 + 3];
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
|
||||
w += 4;
|
||||
++x;
|
||||
}
|
||||
|
||||
// Do partial block at end of row
|
||||
if (w < face.Width)
|
||||
{
|
||||
var cols = face.Width - w;
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < 4; ++r)
|
||||
{
|
||||
h0 = (h + r) * face.Width;
|
||||
for (int c = 0; c < cols; ++c)
|
||||
block[(r * 4) + c] = colors[w + h0 + c];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
}
|
||||
|
||||
h += 4;
|
||||
++y;
|
||||
}
|
||||
|
||||
// Do last partial row
|
||||
if (h < face.Height)
|
||||
{
|
||||
var rows = face.Height - h;
|
||||
w = 0;
|
||||
x = 0;
|
||||
while (w < (face.Width & ~3))
|
||||
{
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < rows; ++r)
|
||||
{
|
||||
var h0 = (h + r) * face.Width;
|
||||
block[(r * 4) + 0] = colors[w + h0 + 0];
|
||||
block[(r * 4) + 1] = colors[w + h0 + 1];
|
||||
block[(r * 4) + 2] = colors[w + h0 + 2];
|
||||
block[(r * 4) + 3] = colors[w + h0 + 3];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
|
||||
w += 4;
|
||||
++x;
|
||||
}
|
||||
|
||||
// Do last partial block
|
||||
if (w < face.Width)
|
||||
{
|
||||
var cols = face.Width - w;
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < rows; ++r)
|
||||
{
|
||||
var h0 = (h + r) * face.Width;
|
||||
for (int c = 0; c < cols; ++c)
|
||||
block[(r * 4) + c] = colors[w + h0 + c];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dxt3 = new Dxt3BitmapContent(face.Width, face.Height);
|
||||
dxt3.SetPixelData(buffer);
|
||||
content.Faces[0][i] = dxt3;
|
||||
}
|
||||
}
|
||||
|
||||
// Maps a 2-bit greyscale to the index required for DXT3
|
||||
// 00 = color0
|
||||
// 01 = color1
|
||||
// 10 = 2/3 * color0 + 1/3 * color1
|
||||
// 11 = 1/3 * color0 + 2/3 * color1
|
||||
static byte[] dxt3Map = new byte[] { 0, 2, 3, 1 };
|
||||
|
||||
// Compress a single 4x4 block from colors into buffer at the given offset
|
||||
static void CompressFontDXT3Block(Vector4[] colors, byte[] buffer, int offset)
|
||||
{
|
||||
// Get the alpha into a 0-15 range
|
||||
int a0 = (int)(colors[0].W * 15.0);
|
||||
int a1 = (int)(colors[1].W * 15.0);
|
||||
int a2 = (int)(colors[2].W * 15.0);
|
||||
int a3 = (int)(colors[3].W * 15.0);
|
||||
int a4 = (int)(colors[4].W * 15.0);
|
||||
int a5 = (int)(colors[5].W * 15.0);
|
||||
int a6 = (int)(colors[6].W * 15.0);
|
||||
int a7 = (int)(colors[7].W * 15.0);
|
||||
int a8 = (int)(colors[8].W * 15.0);
|
||||
int a9 = (int)(colors[9].W * 15.0);
|
||||
int a10 = (int)(colors[10].W * 15.0);
|
||||
int a11 = (int)(colors[11].W * 15.0);
|
||||
int a12 = (int)(colors[12].W * 15.0);
|
||||
int a13 = (int)(colors[13].W * 15.0);
|
||||
int a14 = (int)(colors[14].W * 15.0);
|
||||
int a15 = (int)(colors[15].W * 15.0);
|
||||
|
||||
// Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111
|
||||
a0 = (a0 & 0xC) | (a0 >> 2);
|
||||
a1 = (a1 & 0xC) | (a1 >> 2);
|
||||
a2 = (a2 & 0xC) | (a2 >> 2);
|
||||
a3 = (a3 & 0xC) | (a3 >> 2);
|
||||
a4 = (a4 & 0xC) | (a4 >> 2);
|
||||
a5 = (a5 & 0xC) | (a5 >> 2);
|
||||
a6 = (a6 & 0xC) | (a6 >> 2);
|
||||
a7 = (a7 & 0xC) | (a7 >> 2);
|
||||
a8 = (a8 & 0xC) | (a8 >> 2);
|
||||
a9 = (a9 & 0xC) | (a9 >> 2);
|
||||
a10 = (a10 & 0xC) | (a10 >> 2);
|
||||
a11 = (a11 & 0xC) | (a11 >> 2);
|
||||
a12 = (a12 & 0xC) | (a12 >> 2);
|
||||
a13 = (a13 & 0xC) | (a13 >> 2);
|
||||
a14 = (a14 & 0xC) | (a14 >> 2);
|
||||
a15 = (a15 & 0xC) | (a15 >> 2);
|
||||
|
||||
// 4-bit alpha
|
||||
buffer[offset + 0] = (byte)((a1 << 4) | a0);
|
||||
buffer[offset + 1] = (byte)((a3 << 4) | a2);
|
||||
buffer[offset + 2] = (byte)((a5 << 4) | a4);
|
||||
buffer[offset + 3] = (byte)((a7 << 4) | a6);
|
||||
buffer[offset + 4] = (byte)((a9 << 4) | a8);
|
||||
buffer[offset + 5] = (byte)((a11 << 4) | a10);
|
||||
buffer[offset + 6] = (byte)((a13 << 4) | a12);
|
||||
buffer[offset + 7] = (byte)((a15 << 4) | a14);
|
||||
|
||||
// color0 (transparent)
|
||||
buffer[offset + 8] = 0;
|
||||
buffer[offset + 9] = 0;
|
||||
|
||||
// color1 (white)
|
||||
buffer[offset + 10] = 255;
|
||||
buffer[offset + 11] = 255;
|
||||
|
||||
// Get the red (to be used for green and blue channels as well) into a 0-15 range
|
||||
a0 = (int)(colors[0].X * 15.0);
|
||||
a1 = (int)(colors[1].X * 15.0);
|
||||
a2 = (int)(colors[2].X * 15.0);
|
||||
a3 = (int)(colors[3].X * 15.0);
|
||||
a4 = (int)(colors[4].X * 15.0);
|
||||
a5 = (int)(colors[5].X * 15.0);
|
||||
a6 = (int)(colors[6].X * 15.0);
|
||||
a7 = (int)(colors[7].X * 15.0);
|
||||
a8 = (int)(colors[8].X * 15.0);
|
||||
a9 = (int)(colors[9].X * 15.0);
|
||||
a10 = (int)(colors[10].X * 15.0);
|
||||
a11 = (int)(colors[11].X * 15.0);
|
||||
a12 = (int)(colors[12].X * 15.0);
|
||||
a13 = (int)(colors[13].X * 15.0);
|
||||
a14 = (int)(colors[14].X * 15.0);
|
||||
a15 = (int)(colors[15].X * 15.0);
|
||||
|
||||
// Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111
|
||||
a0 = (a0 & 0xC) | (a0 >> 2);
|
||||
a1 = (a1 & 0xC) | (a1 >> 2);
|
||||
a2 = (a2 & 0xC) | (a2 >> 2);
|
||||
a3 = (a3 & 0xC) | (a3 >> 2);
|
||||
a4 = (a4 & 0xC) | (a4 >> 2);
|
||||
a5 = (a5 & 0xC) | (a5 >> 2);
|
||||
a6 = (a6 & 0xC) | (a6 >> 2);
|
||||
a7 = (a7 & 0xC) | (a7 >> 2);
|
||||
a8 = (a8 & 0xC) | (a8 >> 2);
|
||||
a9 = (a9 & 0xC) | (a9 >> 2);
|
||||
a10 = (a10 & 0xC) | (a10 >> 2);
|
||||
a11 = (a11 & 0xC) | (a11 >> 2);
|
||||
a12 = (a12 & 0xC) | (a12 >> 2);
|
||||
a13 = (a13 & 0xC) | (a13 >> 2);
|
||||
a14 = (a14 & 0xC) | (a14 >> 2);
|
||||
a15 = (a15 & 0xC) | (a15 >> 2);
|
||||
|
||||
// Color indices (00 = color0, 01 = color1, 10 = 2/3 * color0 + 1/3 * color1, 11 = 1/3 * color0 + 2/3 * color1)
|
||||
buffer[offset + 12] = (byte)((dxt3Map[a3 >> 2] << 6) | (dxt3Map[a2 >> 2] << 4) | (dxt3Map[a1 >> 2] << 2) | dxt3Map[a0 >> 2]);
|
||||
buffer[offset + 13] = (byte)((dxt3Map[a7 >> 2] << 6) | (dxt3Map[a6 >> 2] << 4) | (dxt3Map[a5 >> 2] << 2) | dxt3Map[a4 >> 2]);
|
||||
buffer[offset + 14] = (byte)((dxt3Map[a11 >> 2] << 6) | (dxt3Map[a10 >> 2] << 4) | (dxt3Map[a9 >> 2] << 2) | dxt3Map[a8 >> 2]);
|
||||
buffer[offset + 15] = (byte)((dxt3Map[a15 >> 2] << 6) | (dxt3Map[a14 >> 2] << 4) | (dxt3Map[a13 >> 2] << 2) | dxt3Map[a12 >> 2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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 System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for maintaining a list of index values.
|
||||
/// </summary>
|
||||
public sealed class IndexCollection : Collection<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of IndexCollection.
|
||||
/// </summary>
|
||||
public IndexCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a range of indices to the collection.
|
||||
/// </summary>
|
||||
/// <param name="indices">A collection of indices to add.</param>
|
||||
public void AddRange(IEnumerable<int> indices)
|
||||
{
|
||||
foreach (var t in indices)
|
||||
Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for maintaining a list of vertex positions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is designed to collect the vertex positions for a VertexContent object. Use the contents
|
||||
/// of the PositionIndices property (of the contained VertexContent object) to index into the Positions
|
||||
/// property of the parent mesh.
|
||||
/// </remarks>
|
||||
public sealed class IndirectPositionCollection : IList<Vector3>
|
||||
{
|
||||
private readonly VertexChannel<int> _positionIndices;
|
||||
private readonly GeometryContent _geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Number of positions in the collection.
|
||||
/// </summary>
|
||||
/// <value>Number of positions.</value>
|
||||
public int Count
|
||||
{
|
||||
get { return _positionIndices.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position at the specified index.
|
||||
/// </summary>
|
||||
/// <value>Position located at index.</value>
|
||||
public Vector3 this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var remap = _positionIndices[index];
|
||||
return _geometry.Parent.Positions[remap];
|
||||
}
|
||||
set
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this object is read-only.
|
||||
/// </summary>
|
||||
/// <value>true if this object is read-only; false otherwise.</value>
|
||||
bool ICollection<Vector3>.IsReadOnly
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of IndirectPositionCollection.
|
||||
/// </summary>
|
||||
internal IndirectPositionCollection(GeometryContent geom, VertexChannel<int> positionIndices)
|
||||
{
|
||||
_geometry = geom;
|
||||
_positionIndices = positionIndices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified position is in the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">Position being searched for in the collection.</param>
|
||||
/// <returns>true if the position was found; false otherwise.</returns>
|
||||
public bool Contains(Vector3 item)
|
||||
{
|
||||
return IndexOf(item) > -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified positions to an array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">Array of positions to be copied.</param>
|
||||
/// <param name="arrayIndex">Index of the first copied position.</param>
|
||||
public void CopyTo(Vector3[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var vec in this)
|
||||
array[arrayIndex++] = vec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator interface for reading the position values.
|
||||
/// </summary>
|
||||
/// <returns>Interface for enumerating the collection of position values.</returns>
|
||||
public IEnumerator<Vector3> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the specified position in a collection.
|
||||
/// </summary>
|
||||
/// <param name="item">Position being searched for.</param>
|
||||
/// <returns>Index of the specified position or -1 if not found.</returns>
|
||||
public int IndexOf(Vector3 item)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
if (this[i] == item)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
internal Exception Readonly()
|
||||
{
|
||||
return new NotSupportedException("The collection is read only!");
|
||||
}
|
||||
|
||||
void ICollection<Vector3>.Add(Vector3 item)
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
|
||||
void ICollection<Vector3>.Clear()
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
|
||||
bool ICollection<Vector3>.Remove(Vector3 item)
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
|
||||
void IList<Vector3>.Insert(int index, Vector3 item)
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
|
||||
void IList<Vector3>.RemoveAt(int index)
|
||||
{
|
||||
throw Readonly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that can iterate through the collection.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator that can iterate through the collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
#region File Description
|
||||
//-----------------------------------------------------------------------------
|
||||
// LocalizedFontDescription.cs
|
||||
//
|
||||
// Microsoft XNA Community Game Platform
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
#region Using Statements
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Normally, when you add a .spritefont file to your project, this data is
|
||||
/// deserialized into a FontDescription object, which is then built into a
|
||||
/// SpriteFontContent by the FontDescriptionProcessor. But to localize the
|
||||
/// font, we want to add some additional data, so our custom processor can
|
||||
/// know what .resx files it needs to scan. We do this by defining our own
|
||||
/// custom font description class, deriving from the built in FontDescription
|
||||
/// type, and adding a new property to store the resource filenames.
|
||||
/// </summary>
|
||||
public class LocalizedFontDescription : FontDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public LocalizedFontDescription()
|
||||
: base("Arial", 14, 0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a new property to our font description, which will allow us to
|
||||
/// include a ResourceFiles element in the .spritefont XML. We use the
|
||||
/// ContentSerializer attribute to mark this as optional, so existing
|
||||
/// .spritefont files that do not include this ResourceFiles element
|
||||
/// can be imported as well.
|
||||
/// </summary>
|
||||
[ContentSerializer(Optional = true, CollectionItemName = "Resx")]
|
||||
public List<string> ResourceFiles
|
||||
{
|
||||
get { return resourceFiles; }
|
||||
}
|
||||
|
||||
List<string> resourceFiles = new List<string>();
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// 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.Graphics
|
||||
{
|
||||
/// <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>
|
||||
public class MaterialContent : ContentItem
|
||||
{
|
||||
readonly TextureReferenceDictionary _textures;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture collection of the material.
|
||||
/// </summary>
|
||||
/// <value>Collection of textures used by the material.</value>
|
||||
public TextureReferenceDictionary Textures { get { return _textures; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MaterialContent.
|
||||
/// </summary>
|
||||
public MaterialContent()
|
||||
{
|
||||
_textures = new TextureReferenceDictionary();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference type from the OpaqueDataDictionary collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the related opaque data.</typeparam>
|
||||
/// <param name="key">Key of the property being retrieved.</param>
|
||||
/// <returns>The related opaque data.</returns>
|
||||
protected T GetReferenceTypeProperty<T>(string key) where T : class
|
||||
{
|
||||
object value;
|
||||
if (OpaqueData.TryGetValue(key, out value))
|
||||
return (T)value;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value from the Textures collection.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the texture being retrieved.</param>
|
||||
/// <returns>Reference to a texture from the collection.</returns>
|
||||
protected ExternalReference<TextureContent> GetTexture(string key)
|
||||
{
|
||||
ExternalReference<TextureContent> texture;
|
||||
_textures.TryGetValue(key, out texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value type from the OpaqueDataDictionary collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the value being retrieved.</typeparam>
|
||||
/// <param name="key">Key of the value type being retrieved.</param>
|
||||
/// <returns>Index of the value type beng retrieved.</returns>
|
||||
protected Nullable<T> GetValueTypeProperty<T>(string key) where T : struct
|
||||
{
|
||||
object value;
|
||||
if (OpaqueData.TryGetValue(key, out value))
|
||||
return (T)value;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the contained OpaqueDataDictionary object.
|
||||
/// If null is passed, the value is removed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the element being set.</typeparam>
|
||||
/// <param name="key">Name of the key being modified.</param>
|
||||
/// <param name="value">Value being set.</param>
|
||||
protected void SetProperty<T>(string key, T value)
|
||||
{
|
||||
if (value != null)
|
||||
OpaqueData[key] = value;
|
||||
else
|
||||
OpaqueData.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the contained TextureReferenceDictionary object.
|
||||
/// If null is passed, the value is removed.
|
||||
/// </summary>
|
||||
/// <param name="key">Name of the key being modified.</param>
|
||||
/// <param name="value">Value being set.</param>
|
||||
/// <remarks>The key value differs depending on the type of attached dictionary.
|
||||
/// If attached to a BasicMaterialContent dictionary (which becomes a BasicEffect object at run time), the value for the Texture key is used as the texture for the BasicEffect runtime object. Other keys are ignored.
|
||||
/// If attached to a EffectMaterialContent dictionary, key names are the texture names used by the effect. These names are dependent upon the author of the effect object.</remarks>
|
||||
protected void SetTexture(string key, ExternalReference<TextureContent> value)
|
||||
{
|
||||
if (value != null)
|
||||
_textures[key] = value;
|
||||
else
|
||||
_textures.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to make a copy of a material.
|
||||
/// </summary>
|
||||
/// <returns>A clone of the material.</returns>
|
||||
public MaterialContent Clone()
|
||||
{
|
||||
// Construct it via reflection.
|
||||
var clone = (MaterialContent)Activator.CreateInstance(GetType());
|
||||
|
||||
// Give it the same identity as the original material.
|
||||
clone.Name = Name;
|
||||
clone.Identity = Identity;
|
||||
|
||||
// Just copy the opaque data and textures which should
|
||||
// result in the same properties being set if the material
|
||||
// is implemented correctly.
|
||||
foreach (var pair in Textures)
|
||||
clone.Textures.Add(pair.Key, pair.Value);
|
||||
foreach (var pair in OpaqueData)
|
||||
clone.OpaqueData.Add(pair.Key, pair.Value);
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public sealed class MeshBuilder
|
||||
{
|
||||
private readonly MeshContent _meshContent;
|
||||
|
||||
private MaterialContent _currentMaterial;
|
||||
private OpaqueDataDictionary _currentOpaqueData;
|
||||
private bool _geometryDirty;
|
||||
private GeometryContent _currentGeometryContent;
|
||||
|
||||
private readonly List<VertexChannel> _vertexChannels;
|
||||
private readonly List<object> _vertexChannelData;
|
||||
|
||||
private bool _finishedCreation;
|
||||
private bool _finishedMesh;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current value for position merging of the mesh.
|
||||
/// </summary>
|
||||
public bool MergeDuplicatePositions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tolerance for <see cref="MergeDuplicatePositions"/>.
|
||||
/// </summary>
|
||||
public float MergePositionTolerance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the current <see cref="MeshContent"/> object being processed.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _meshContent.Name;
|
||||
}
|
||||
set
|
||||
{
|
||||
_meshContent.Name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the triangle winding order of the specified mesh.
|
||||
/// </summary>
|
||||
public bool SwapWindingOrder { get; set; }
|
||||
|
||||
|
||||
private MeshBuilder(string name)
|
||||
{
|
||||
_meshContent = new MeshContent();
|
||||
_vertexChannels = new List<VertexChannel>();
|
||||
_vertexChannelData = new List<object>();
|
||||
_currentGeometryContent = new GeometryContent();
|
||||
_currentOpaqueData = new OpaqueDataDictionary();
|
||||
_geometryDirty = true;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a vertex into the index collection.
|
||||
/// </summary>
|
||||
/// <param name="indexIntoVertexCollection">Index of the inserted vertex, in the collection.
|
||||
/// This corresponds to the value returned by <see cref="CreatePosition(float,float,float)"/>.</param>
|
||||
public void AddTriangleVertex(int indexIntoVertexCollection)
|
||||
{
|
||||
if (_finishedMesh)
|
||||
throw new InvalidOperationException( "This MeshBuilder can no longer be used because FinishMesh has been called.");
|
||||
|
||||
_finishedCreation = true;
|
||||
|
||||
if (_geometryDirty)
|
||||
{
|
||||
_currentGeometryContent = new GeometryContent();
|
||||
_currentGeometryContent.Material = _currentMaterial;
|
||||
foreach (var kvp in _currentOpaqueData)
|
||||
_currentGeometryContent.OpaqueData.Add(kvp.Key, kvp.Value);
|
||||
|
||||
// we have to copy our vertex channels to the new geometry
|
||||
foreach (var channel in _vertexChannels)
|
||||
{
|
||||
_currentGeometryContent.Vertices.Channels.Add(channel.Name, channel.ElementType, null);
|
||||
}
|
||||
_meshContent.Geometry.Add(_currentGeometryContent);
|
||||
_geometryDirty = false;
|
||||
|
||||
}
|
||||
// Add the vertex to the mesh and then add the vertex position to the indices list
|
||||
var pos = _currentGeometryContent.Vertices.Add(indexIntoVertexCollection);
|
||||
|
||||
// Then add the data for the other channels
|
||||
for (var i = 0; i < _vertexChannels.Count; i++)
|
||||
{
|
||||
var channel = _currentGeometryContent.Vertices.Channels[i];
|
||||
var data = _vertexChannelData[i];
|
||||
if (data == null)
|
||||
throw new InvalidOperationException(string.Format("Missing vertex channel data for channel {0}", channel.Name));
|
||||
|
||||
channel.Items.Add(data);
|
||||
}
|
||||
|
||||
_currentGeometryContent.Indices.Add(pos);
|
||||
}
|
||||
|
||||
public int CreateVertexChannel<T>(string usage)
|
||||
{
|
||||
if (_finishedMesh)
|
||||
throw new InvalidOperationException("This MeshBuilder can no longer be used because FinishMesh has been called.");
|
||||
if (_finishedCreation)
|
||||
throw new InvalidOperationException("Functions starting with 'Create' must be called before calling AddTriangleVertex");
|
||||
|
||||
var channel = new VertexChannel<T>(usage);
|
||||
_vertexChannels.Add(channel);
|
||||
_vertexChannelData.Add(default(T));
|
||||
|
||||
_currentGeometryContent.Vertices.Channels.Add<T>(usage, null);
|
||||
|
||||
return _vertexChannels.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the specified vertex position into the vertex channel.
|
||||
/// </summary>
|
||||
/// <param name="x">Value of the x component of the vector.</param>
|
||||
/// <param name="y">Value of the y component of the vector.</param>
|
||||
/// <param name="z">Value of the z component of the vector.</param>
|
||||
/// <returns>Index of the inserted vertex.</returns>
|
||||
public int CreatePosition(float x, float y, float z)
|
||||
{
|
||||
return CreatePosition(new Vector3(x, y, z));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the specified vertex position into the vertex channel at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="pos">Value of the vertex being inserted.</param>
|
||||
/// <returns>Index of the vertex being inserted.</returns>
|
||||
public int CreatePosition(Vector3 pos)
|
||||
{
|
||||
if (_finishedMesh)
|
||||
throw new InvalidOperationException( "This MeshBuilder can no longer be used because FinishMesh has been called.");
|
||||
if (_finishedCreation)
|
||||
throw new InvalidOperationException("Functions starting with 'Create' must be called before calling AddTriangleVertex");
|
||||
|
||||
_meshContent.Positions.Add(pos);
|
||||
return _meshContent.Positions.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the creation of a mesh.
|
||||
/// </summary>
|
||||
/// <returns>Resultant mesh.</returns>
|
||||
public MeshContent FinishMesh()
|
||||
{
|
||||
if (_finishedMesh)
|
||||
return _meshContent;
|
||||
|
||||
if (MergeDuplicatePositions)
|
||||
MeshHelper.MergeDuplicatePositions(_meshContent, MergePositionTolerance);
|
||||
|
||||
MeshHelper.MergeDuplicateVertices(_meshContent);
|
||||
|
||||
MeshHelper.CalculateNormals(_meshContent, false);
|
||||
if (SwapWindingOrder)
|
||||
MeshHelper.SwapWindingOrder(_meshContent);
|
||||
|
||||
_finishedMesh = true;
|
||||
return _meshContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the material for the next triangles.
|
||||
/// </summary>
|
||||
/// <param name="material">Material for the next triangles.</param>
|
||||
/// <remarks>
|
||||
/// Sets the material for the triangles being defined next. This material
|
||||
/// and the opaque data dictionary, set with <see cref="SetOpaqueData"/>
|
||||
/// define the <see cref="GeometryContent"/> object containing the next
|
||||
/// triangles. When you set a new material or opaque data dictionary the
|
||||
/// triangles you add afterwards will belong to a new
|
||||
/// <see cref="GeometryContent"/> object.
|
||||
/// </remarks>
|
||||
public void SetMaterial(MaterialContent material)
|
||||
{
|
||||
if (_currentMaterial == material)
|
||||
return;
|
||||
|
||||
_currentMaterial = material;
|
||||
_geometryDirty = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the opaque data for the next triangles.
|
||||
/// </summary>
|
||||
/// <param name="opaqueData">Opaque data dictionary for the next triangles.</param>
|
||||
/// <remarks>
|
||||
/// Sets the opaque data dictionary for the triangles being defined next. This dictionary
|
||||
/// and the material, set with <see cref="SetMaterial"/>, define the <see cref="GeometryContent"/>
|
||||
/// object containing the next triangles. When you set a new material or opaque data dictionary
|
||||
/// the triangles you add afterwards will belong to a new <see cref="GeometryContent"/> object.
|
||||
/// </remarks>
|
||||
public void SetOpaqueData(OpaqueDataDictionary opaqueData)
|
||||
{
|
||||
if (_currentOpaqueData == opaqueData)
|
||||
return;
|
||||
|
||||
_currentOpaqueData = opaqueData;
|
||||
_geometryDirty = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified vertex data with new data.
|
||||
/// </summary>
|
||||
/// <param name="vertexDataIndex">Index of the vertex data channel being set. This should match the index returned by CreateVertexChannel.</param>
|
||||
/// <param name="channelData">New data values for the vertex data. The data type being set must match the data type for the vertex channel specified by vertexDataIndex.</param>
|
||||
public void SetVertexChannelData(int vertexDataIndex, object channelData)
|
||||
{
|
||||
if (_currentGeometryContent.Vertices.Channels[vertexDataIndex].ElementType != channelData.GetType())
|
||||
throw new InvalidOperationException(string.Format("Channel {0} data has a different type from input. Expected: {1}. Actual: {2}",
|
||||
vertexDataIndex, _currentGeometryContent.Vertices.Channels[vertexDataIndex].ElementType, channelData.GetType()));
|
||||
|
||||
_vertexChannelData[vertexDataIndex] = channelData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the creation of a mesh.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the mesh.</param>
|
||||
/// <returns>Object used when building the mesh.</returns>
|
||||
public static MeshBuilder StartMesh(string name)
|
||||
{
|
||||
return new MeshBuilder(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides properties and methods that define various aspects of a mesh.
|
||||
/// </summary>
|
||||
public class MeshContent : NodeContent
|
||||
{
|
||||
GeometryContentCollection geometry;
|
||||
PositionCollection positions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of geometry batches for the mesh.
|
||||
/// </summary>
|
||||
public GeometryContentCollection Geometry
|
||||
{
|
||||
get
|
||||
{
|
||||
return geometry;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of vertex position values.
|
||||
/// </summary>
|
||||
public PositionCollection Positions
|
||||
{
|
||||
get
|
||||
{
|
||||
return positions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MeshContent.
|
||||
/// </summary>
|
||||
public MeshContent()
|
||||
{
|
||||
geometry = new GeometryContentCollection(this);
|
||||
positions = new PositionCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a transform directly to position and normal channels. Node transforms are unaffected.
|
||||
/// </summary>
|
||||
internal void TransformContents(ref Matrix xform)
|
||||
{
|
||||
// Transform positions
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
positions[i] = Vector3.Transform(positions[i], xform);
|
||||
|
||||
// Transform all vectors too:
|
||||
// Normals are "tangent covectors", which need to be transformed using the
|
||||
// transpose of the inverse matrix!
|
||||
Matrix inverseTranspose = Matrix.Transpose(Matrix.Invert(xform));
|
||||
foreach (var geom in geometry)
|
||||
{
|
||||
foreach (var channel in geom.Vertices.Channels)
|
||||
{
|
||||
var vector3Channel = channel as VertexChannel<Vector3>;
|
||||
if (vector3Channel == null)
|
||||
continue;
|
||||
|
||||
if (channel.Name.StartsWith("Normal") ||
|
||||
channel.Name.StartsWith("Binormal") ||
|
||||
channel.Name.StartsWith("Tangent"))
|
||||
{
|
||||
for (int i = 0; i < vector3Channel.Count; i++)
|
||||
{
|
||||
Vector3 normal = vector3Channel[i];
|
||||
Vector3.TransformNormal(ref normal, ref inverseTranspose, out normal);
|
||||
Vector3.Normalize(ref normal, out normal);
|
||||
vector3Channel[i] = normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Swap winding order when faces are mirrored.
|
||||
if (MeshHelper.IsLeftHanded(ref xform))
|
||||
MeshHelper.SwapWindingOrder(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+712
@@ -0,0 +1,712 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public static class MeshHelper
|
||||
{
|
||||
static bool IsFinite(float v)
|
||||
{
|
||||
return !float.IsInfinity(v) && !float.IsNaN(v);
|
||||
}
|
||||
|
||||
static bool IsFinite(this Vector3 v)
|
||||
{
|
||||
return IsFinite(v.X) && IsFinite(v.Y) && IsFinite(v.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates vertex normals by accumulation of triangle face normals.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh which will recieve the normals.</param>
|
||||
/// <param name="overwriteExistingNormals">Overwrite or skip over geometry with existing normals.</param>
|
||||
/// <remarks>
|
||||
/// This calls <see cref="CalculateNormals(GeometryContent, bool)"/> to do the work.
|
||||
/// </remarks>
|
||||
public static void CalculateNormals(MeshContent mesh, bool overwriteExistingNormals)
|
||||
{
|
||||
foreach (var geom in mesh.Geometry)
|
||||
CalculateNormals(geom, overwriteExistingNormals);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates vertex normals by accumulation of triangle face normals.
|
||||
/// </summary>
|
||||
/// <param name="geom">The geometry which will recieve the normals.</param>
|
||||
/// <param name="overwriteExistingNormals">Overwrite or skip over geometry with existing normals.</param>
|
||||
/// <remarks>
|
||||
/// We use a "Mean Weighted Equally" method generate vertex normals from triangle
|
||||
/// face normals. If normal cannot be calculated from the geometry we set it to zero.
|
||||
/// </remarks>
|
||||
public static void CalculateNormals(GeometryContent geom, bool overwriteExistingNormals)
|
||||
{
|
||||
VertexChannel<Vector3> channel;
|
||||
// Look for an existing normals channel.
|
||||
if (!geom.Vertices.Channels.Contains(VertexChannelNames.Normal()))
|
||||
{
|
||||
// We don't have existing normals, so add a new channel.
|
||||
channel = geom.Vertices.Channels.Add<Vector3>(VertexChannelNames.Normal(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're not supposed to overwrite the existing
|
||||
// normals then we're done here.
|
||||
if (!overwriteExistingNormals)
|
||||
return;
|
||||
|
||||
channel = geom.Vertices.Channels.Get<Vector3>(VertexChannelNames.Normal());
|
||||
}
|
||||
|
||||
var positionIndices = geom.Vertices.PositionIndices;
|
||||
Debug.Assert(positionIndices.Count == channel.Count, "The position and channel sizes were different!");
|
||||
|
||||
// Accumulate all the triangle face normals for each vertex.
|
||||
var normals = new Vector3[positionIndices.Count];
|
||||
for (var i = 0; i < geom.Indices.Count; i += 3)
|
||||
{
|
||||
var ia = geom.Indices[i + 0];
|
||||
var ib = geom.Indices[i + 1];
|
||||
var ic = geom.Indices[i + 2];
|
||||
|
||||
var aa = geom.Vertices.Positions[ia];
|
||||
var bb = geom.Vertices.Positions[ib];
|
||||
var cc = geom.Vertices.Positions[ic];
|
||||
|
||||
var faceNormal = Vector3.Cross(cc - bb, bb - aa);
|
||||
var len = faceNormal.Length();
|
||||
if (len > 0.0f)
|
||||
{
|
||||
faceNormal = faceNormal / len;
|
||||
|
||||
// We are using the "Mean Weighted Equally" method where each
|
||||
// face has an equal weight in the final normal calculation.
|
||||
//
|
||||
// We could maybe switch to "Mean Weighted by Angle" which is said
|
||||
// to look best in most cases, but is more expensive to calculate.
|
||||
//
|
||||
// There is also an idea of weighting by triangle area, but IMO the
|
||||
// triangle area doesn't always have a direct relationship to the
|
||||
// shape of a mesh.
|
||||
//
|
||||
// For more ideas see:
|
||||
//
|
||||
// "A Comparison of Algorithms for Vertex Normal Computation"
|
||||
// by Shuangshuang Jin, Robert R. Lewis, David West.
|
||||
//
|
||||
|
||||
normals[positionIndices[ia]] += faceNormal;
|
||||
normals[positionIndices[ib]] += faceNormal;
|
||||
normals[positionIndices[ic]] += faceNormal;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the gathered vertex normals.
|
||||
for (var i = 0; i < normals.Length; i++)
|
||||
{
|
||||
var normal = normals[i];
|
||||
var len = normal.Length();
|
||||
if (len > 0.0f)
|
||||
normals[i] = normal / len;
|
||||
else
|
||||
{
|
||||
// TODO: It would be nice to be able to log this to
|
||||
// the pipeline so that it can be fixed in the model.
|
||||
|
||||
// TODO: We could maybe void this by a better algorithm
|
||||
// above for generating the normals.
|
||||
|
||||
// We have a zero length normal. You can argue that putting
|
||||
// anything here is better than nothing, but by leaving it to
|
||||
// zero it allows the caller to detect this and react to it.
|
||||
normals[i] = Vector3.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the new normals on the vertex channel.
|
||||
for (var i = 0; i < channel.Count; i++)
|
||||
channel[i] = normals[geom.Indices[i]];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the tangents and binormals (tangent frames) for each vertex in the mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh which will have add tangent and binormal channels added.</param>
|
||||
/// <param name="textureCoordinateChannelName">The Vector2 texture coordinate channel used to generate tangent frames.</param>
|
||||
/// <param name="tangentChannelName"></param>
|
||||
/// <param name="binormalChannelName"></param>
|
||||
public static void CalculateTangentFrames(MeshContent mesh, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName)
|
||||
{
|
||||
foreach (var geom in mesh.Geometry)
|
||||
CalculateTangentFrames(geom, textureCoordinateChannelName, tangentChannelName, binormalChannelName);
|
||||
}
|
||||
|
||||
public static void CalculateTangentFrames(GeometryContent geom, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName)
|
||||
{
|
||||
var verts = geom.Vertices;
|
||||
var indices = geom.Indices;
|
||||
var channels = geom.Vertices.Channels;
|
||||
|
||||
var normals = channels.Get<Vector3>(VertexChannelNames.Normal(0));
|
||||
var uvs = channels.Get<Vector2>(textureCoordinateChannelName);
|
||||
|
||||
Vector3[] tangents, bitangents;
|
||||
CalculateTangentFrames(verts.Positions, indices, normals, uvs, out tangents, out bitangents);
|
||||
|
||||
// All the indices are 1:1 with the others, so we
|
||||
// can just add the new channels in place.
|
||||
|
||||
if (!string.IsNullOrEmpty(tangentChannelName))
|
||||
channels.Add(tangentChannelName, tangents);
|
||||
|
||||
if (!string.IsNullOrEmpty(binormalChannelName))
|
||||
channels.Add(binormalChannelName, bitangents);
|
||||
}
|
||||
|
||||
public static void CalculateTangentFrames(IList<Vector3> positions,
|
||||
IList<int> indices,
|
||||
IList<Vector3> normals,
|
||||
IList<Vector2> textureCoords,
|
||||
out Vector3[] tangents,
|
||||
out Vector3[] bitangents)
|
||||
{
|
||||
// Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”.
|
||||
// Terathon Software 3D Graphics Library, 2001.
|
||||
// http://www.terathon.com/code/tangent.html
|
||||
|
||||
// Hegde, Siddharth. "Messing with Tangent Space". Gamasutra, 2007.
|
||||
// http://www.gamasutra.com/view/feature/129939/messing_with_tangent_space.php
|
||||
|
||||
var numVerts = positions.Count;
|
||||
var numIndices = indices.Count;
|
||||
|
||||
var tan1 = new Vector3[numVerts];
|
||||
var tan2 = new Vector3[numVerts];
|
||||
|
||||
for (var index = 0; index < numIndices; index += 3)
|
||||
{
|
||||
var i1 = indices[index + 0];
|
||||
var i2 = indices[index + 1];
|
||||
var i3 = indices[index + 2];
|
||||
|
||||
var w1 = textureCoords[i1];
|
||||
var w2 = textureCoords[i2];
|
||||
var w3 = textureCoords[i3];
|
||||
|
||||
var s1 = w2.X - w1.X;
|
||||
var s2 = w3.X - w1.X;
|
||||
var t1 = w2.Y - w1.Y;
|
||||
var t2 = w3.Y - w1.Y;
|
||||
|
||||
var denom = s1 * t2 - s2 * t1;
|
||||
if (Math.Abs(denom) < float.Epsilon)
|
||||
{
|
||||
// The triangle UVs are zero sized one dimension.
|
||||
//
|
||||
// So we cannot calculate the vertex tangents for this
|
||||
// one trangle, but maybe it can with other trangles.
|
||||
continue;
|
||||
}
|
||||
|
||||
var r = 1.0f / denom;
|
||||
Debug.Assert(IsFinite(r), "Bad r!");
|
||||
|
||||
var v1 = positions[i1];
|
||||
var v2 = positions[i2];
|
||||
var v3 = positions[i3];
|
||||
|
||||
var x1 = v2.X - v1.X;
|
||||
var x2 = v3.X - v1.X;
|
||||
var y1 = v2.Y - v1.Y;
|
||||
var y2 = v3.Y - v1.Y;
|
||||
var z1 = v2.Z - v1.Z;
|
||||
var z2 = v3.Z - v1.Z;
|
||||
|
||||
var sdir = new Vector3()
|
||||
{
|
||||
X = (t2 * x1 - t1 * x2) * r,
|
||||
Y = (t2 * y1 - t1 * y2) * r,
|
||||
Z = (t2 * z1 - t1 * z2) * r,
|
||||
};
|
||||
|
||||
var tdir = new Vector3()
|
||||
{
|
||||
X = (s1 * x2 - s2 * x1) * r,
|
||||
Y = (s1 * y2 - s2 * y1) * r,
|
||||
Z = (s1 * z2 - s2 * z1) * r,
|
||||
};
|
||||
|
||||
tan1[i1] += sdir;
|
||||
Debug.Assert(tan1[i1].IsFinite(), "Bad tan1[i1]!");
|
||||
tan1[i2] += sdir;
|
||||
Debug.Assert(tan1[i2].IsFinite(), "Bad tan1[i2]!");
|
||||
tan1[i3] += sdir;
|
||||
Debug.Assert(tan1[i3].IsFinite(), "Bad tan1[i3]!");
|
||||
|
||||
tan2[i1] += tdir;
|
||||
Debug.Assert(tan2[i1].IsFinite(), "Bad tan2[i1]!");
|
||||
tan2[i2] += tdir;
|
||||
Debug.Assert(tan2[i2].IsFinite(), "Bad tan2[i2]!");
|
||||
tan2[i3] += tdir;
|
||||
Debug.Assert(tan2[i3].IsFinite(), "Bad tan2[i3]!");
|
||||
}
|
||||
|
||||
tangents = new Vector3[numVerts];
|
||||
bitangents = new Vector3[numVerts];
|
||||
|
||||
// At this point we have all the vectors accumulated, but we need to average
|
||||
// them all out. So we loop through all the final verts and do a Gram-Schmidt
|
||||
// orthonormalize, then make sure they're all unit length.
|
||||
for (var i = 0; i < numVerts; i++)
|
||||
{
|
||||
var n = normals[i];
|
||||
Debug.Assert(n.IsFinite(), "Bad normal!");
|
||||
Debug.Assert(n.Length() >= 0.9999f, "Bad normal!");
|
||||
|
||||
var t = tan1[i];
|
||||
if (t.LengthSquared() < float.Epsilon)
|
||||
{
|
||||
// TODO: Ideally we could spit out a warning to the
|
||||
// content logging here!
|
||||
|
||||
// We couldn't find a good tanget for this vertex.
|
||||
//
|
||||
// Rather than set them to zero which could produce
|
||||
// errors in other parts of the pipeline, we just take
|
||||
// a guess at something that may look ok.
|
||||
|
||||
t = Vector3.Cross(n, Vector3.UnitX);
|
||||
if (t.LengthSquared() < float.Epsilon)
|
||||
t = Vector3.Cross(n, Vector3.UnitY);
|
||||
|
||||
tangents[i] = Vector3.Normalize(t);
|
||||
bitangents[i] = Vector3.Cross(n, tangents[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gram-Schmidt orthogonalize
|
||||
// TODO: This can be zero can cause NaNs on
|
||||
// normalize... how do we fix this?
|
||||
var tangent = t - n * Vector3.Dot(n, t);
|
||||
tangent = Vector3.Normalize(tangent);
|
||||
Debug.Assert(tangent.IsFinite(), "Bad tangent!");
|
||||
tangents[i] = tangent;
|
||||
|
||||
// Calculate handedness
|
||||
var w = (Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0F) ? -1.0F : 1.0F;
|
||||
Debug.Assert(IsFinite(w), "Bad handedness!");
|
||||
|
||||
// Calculate the bitangent
|
||||
var bitangent = Vector3.Cross(n, tangent) * w;
|
||||
Debug.Assert(bitangent.IsFinite(), "Bad bitangent!");
|
||||
bitangents[i] = bitangent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for the root bone of the skeletion.
|
||||
/// </summary>
|
||||
/// <param name="node">The node from which to begin the search for the skeleton.</param>
|
||||
/// <returns>The root bone of the skeletion or null if none is found.</returns>
|
||||
public static BoneContent FindSkeleton(NodeContent node)
|
||||
{
|
||||
// We should always get a node to search!
|
||||
if (node == null)
|
||||
throw new ArgumentNullException("node");
|
||||
|
||||
// Search up thru the hierarchy.
|
||||
for (; node != null; node = node.Parent)
|
||||
{
|
||||
// First if this node is a bone then search up for the root.
|
||||
var root = node as BoneContent;
|
||||
if (root != null)
|
||||
{
|
||||
while (root.Parent is BoneContent)
|
||||
root = (BoneContent)root.Parent;
|
||||
return root;
|
||||
}
|
||||
|
||||
// Next try searching the children for a root bone.
|
||||
foreach (var nodeContent in node.Children)
|
||||
{
|
||||
var bone = nodeContent as BoneContent;
|
||||
if (bone == null)
|
||||
continue;
|
||||
|
||||
// If we found a bone
|
||||
if (root != null)
|
||||
throw new InvalidContentException("DuplicateSkeleton", node.Identity);
|
||||
|
||||
// This is our new root.
|
||||
root = bone;
|
||||
}
|
||||
|
||||
// If we found a root bone then return it, else
|
||||
// we continue the search to the node parent.
|
||||
if (root != null)
|
||||
return root;
|
||||
}
|
||||
|
||||
// We didn't find any bones!
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses a skeleton depth-first and builds a list of its bones.
|
||||
/// </summary>
|
||||
public static IList<BoneContent> FlattenSkeleton(BoneContent skeleton)
|
||||
{
|
||||
if (skeleton == null)
|
||||
throw new ArgumentNullException("skeleton");
|
||||
|
||||
var results = new List<BoneContent>();
|
||||
var work = new Stack<NodeContent>(new[] { skeleton });
|
||||
while (work.Count > 0)
|
||||
{
|
||||
var top = work.Pop();
|
||||
var bone = top as BoneContent;
|
||||
if (bone != null)
|
||||
results.Add(bone);
|
||||
|
||||
for (var i = top.Children.Count - 1; i >= 0; i--)
|
||||
work.Push(top.Children[i]);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge any positions in the <see cref="PositionCollection"/> of the
|
||||
/// specified mesh that are at a distance less than the specified tolerance
|
||||
/// from each other.
|
||||
/// </summary>
|
||||
/// <param name="mesh">Mesh to be processed.</param>
|
||||
/// <param name="tolerance">Tolerance value that determines how close
|
||||
/// positions must be to each other to be merged.</param>
|
||||
/// <remarks>
|
||||
/// This method will also update the <see cref="VertexContent.PositionIndices"/>
|
||||
/// in the <see cref="GeometryContent"/> of the specified mesh.
|
||||
/// </remarks>
|
||||
public static void MergeDuplicatePositions(MeshContent mesh, float tolerance)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
// TODO Improve performance with spatial partitioning scheme
|
||||
var indexLists = new List<IndexUpdateList>();
|
||||
foreach (var geom in mesh.Geometry)
|
||||
{
|
||||
var list = new IndexUpdateList(geom.Vertices.PositionIndices);
|
||||
indexLists.Add(list);
|
||||
}
|
||||
|
||||
for (var i = mesh.Positions.Count - 1; i >= 1; i--)
|
||||
{
|
||||
var pi = mesh.Positions[i];
|
||||
for (var j = i - 1; j >= 0; j--)
|
||||
{
|
||||
var pj = mesh.Positions[j];
|
||||
if (Vector3.Distance(pi, pj) <= tolerance)
|
||||
{
|
||||
foreach (var list in indexLists)
|
||||
list.Update(i, j);
|
||||
mesh.Positions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge vertices with the same <see cref="VertexContent.PositionIndices"/> and
|
||||
/// <see cref="VertexChannel"/> data within the specified
|
||||
/// <see cref="GeometryContent"/>.
|
||||
/// </summary>
|
||||
/// <param name="geometry">Geometry to be processed.</param>
|
||||
public static void MergeDuplicateVertices(GeometryContent geometry)
|
||||
{
|
||||
if (geometry == null)
|
||||
throw new ArgumentNullException("geometry");
|
||||
|
||||
var verts = geometry.Vertices;
|
||||
var hashMap = new Dictionary<int, List<VertexData>>();
|
||||
|
||||
var indices = new IndexUpdateList(geometry.Indices);
|
||||
var vIndex = 0;
|
||||
|
||||
for (var i = 0; i < geometry.Indices.Count; i++)
|
||||
{
|
||||
var iIndex = geometry.Indices[i];
|
||||
var iData = new VertexData
|
||||
{
|
||||
Index = iIndex,
|
||||
PositionIndex = verts.PositionIndices[vIndex],
|
||||
ChannelData = new object[verts.Channels.Count]
|
||||
};
|
||||
|
||||
for (var channel = 0; channel < verts.Channels.Count; channel++)
|
||||
iData.ChannelData[channel] = verts.Channels[channel][vIndex];
|
||||
|
||||
var hash = iData.ComputeHash();
|
||||
|
||||
var merged = false;
|
||||
List<VertexData> candidates;
|
||||
if (hashMap.TryGetValue(hash, out candidates))
|
||||
{
|
||||
for (var candidateIndex = 0; candidateIndex < candidates.Count; candidateIndex++)
|
||||
{
|
||||
var c = candidates[candidateIndex];
|
||||
if (!iData.ContentEquals(c))
|
||||
continue;
|
||||
|
||||
// Match! Update the corresponding indices and remove the vertex
|
||||
indices.Update(iIndex, c.Index);
|
||||
verts.RemoveAt(vIndex);
|
||||
merged = true;
|
||||
}
|
||||
if (!merged)
|
||||
candidates.Add(iData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no vertices with the same hash yet, create a new list for the data
|
||||
hashMap.Add(hash, new List<VertexData> { iData });
|
||||
}
|
||||
|
||||
if (!merged)
|
||||
vIndex++;
|
||||
}
|
||||
|
||||
// update the indices because of the vertices we removed
|
||||
indices.Pack();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge vertices with the same <see cref="VertexContent.PositionIndices"/> and
|
||||
/// <see cref="VertexChannel"/> data within the <see cref="MeshContent.Geometry"/>
|
||||
/// of this mesh. If you want to merge positions too, call
|
||||
/// <see cref="MergeDuplicatePositions"/> on your mesh before this function.
|
||||
/// </summary>
|
||||
/// <param name="mesh">Mesh to be processed</param>
|
||||
public static void MergeDuplicateVertices(MeshContent mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
foreach (var geom in mesh.Geometry)
|
||||
MergeDuplicateVertices(geom);
|
||||
}
|
||||
|
||||
public static void OptimizeForCache(MeshContent mesh)
|
||||
{
|
||||
// We don't throw here as non-optimized still works.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the triangle winding order of the mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh which will be modified.</param>
|
||||
/// <remarks>
|
||||
/// This method is useful when changing the direction of backface culling
|
||||
/// like when switching between left/right handed coordinate systems.
|
||||
/// </remarks>
|
||||
public static void SwapWindingOrder(MeshContent mesh)
|
||||
{
|
||||
// Gotta have a mesh to run!
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
foreach (var geom in mesh.Geometry)
|
||||
{
|
||||
for (var i = 0; i < geom.Indices.Count; i += 3)
|
||||
{
|
||||
var first = geom.Indices[i];
|
||||
var last = geom.Indices[i+2];
|
||||
geom.Indices[i] = last;
|
||||
geom.Indices[i+2] = first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the contents of a node and its descendants.
|
||||
/// </summary>
|
||||
/// <remarks>The node transforms themselves are unaffected.</remarks>
|
||||
/// <param name="scene">The root node of the scene to transform.</param>
|
||||
/// <param name="transform">The transform matrix to apply to the scene.</param>
|
||||
public static void TransformScene(NodeContent scene, Matrix transform)
|
||||
{
|
||||
if (scene == null)
|
||||
throw new ArgumentException("scene");
|
||||
|
||||
// If the transformation is an identity matrix, this is a no-op and
|
||||
// we can save ourselves a bunch of work in the first place.
|
||||
if (transform == Matrix.Identity)
|
||||
return;
|
||||
|
||||
var inverseTransform = Matrix.Invert(transform);
|
||||
|
||||
var work = new Stack<NodeContent>();
|
||||
work.Push(scene);
|
||||
|
||||
while (work.Count > 0)
|
||||
{
|
||||
var node = work.Pop();
|
||||
foreach (var child in node.Children)
|
||||
work.Push(child);
|
||||
|
||||
// Transform the mesh content.
|
||||
var mesh = node as MeshContent;
|
||||
if (mesh != null)
|
||||
mesh.TransformContents(ref transform);
|
||||
|
||||
// Transform local coordinate system using "similarity transform".
|
||||
node.Transform = inverseTransform * node.Transform * transform;
|
||||
|
||||
// Transform animations.
|
||||
foreach (var animationContent in node.Animations.Values)
|
||||
foreach (var animationChannel in animationContent.Channels.Values)
|
||||
for (int i = 0; i < animationChannel.Count; i++)
|
||||
animationChannel[i].Transform = inverseTransform * animationChannel[i].Transform * transform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified transform is left-handed.
|
||||
/// </summary>
|
||||
/// <param name="xform">The transform.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="xform"/> is left-handed; otherwise,
|
||||
/// <see langword="false"/> if <paramref name="xform"/> is right-handed.
|
||||
/// </returns>
|
||||
internal static bool IsLeftHanded(ref Matrix xform)
|
||||
{
|
||||
// Check sign of determinant of upper-left 3x3 matrix:
|
||||
// positive determinant ... right-handed
|
||||
// negative determinant ... left-handed
|
||||
|
||||
// Since XNA does not have a 3x3 matrix, use the "scalar triple product"
|
||||
// (see http://en.wikipedia.org/wiki/Triple_product) to calculate the
|
||||
// determinant.
|
||||
float d = Vector3.Dot(xform.Right, Vector3.Cross(xform.Forward, xform.Up));
|
||||
return d < 0.0f;
|
||||
}
|
||||
|
||||
#region Private helpers
|
||||
|
||||
private static void UpdatePositionIndices(MeshContent mesh, int from, int to)
|
||||
{
|
||||
foreach (var geom in mesh.Geometry)
|
||||
{
|
||||
for (var i = 0; i < geom.Vertices.PositionIndices.Count; i++)
|
||||
{
|
||||
var index = geom.Vertices.PositionIndices[i];
|
||||
if (index == from)
|
||||
geom.Vertices.PositionIndices[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VertexData
|
||||
{
|
||||
public int Index;
|
||||
public int PositionIndex;
|
||||
public object[] ChannelData;
|
||||
|
||||
// Compute a hash based on PositionIndex and ChannelData
|
||||
public int ComputeHash()
|
||||
{
|
||||
var hash = PositionIndex;
|
||||
foreach (var channel in ChannelData)
|
||||
hash ^= channel.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Check equality on PositionIndex and ChannelData
|
||||
public bool ContentEquals(VertexData other)
|
||||
{
|
||||
if (PositionIndex != other.PositionIndex)
|
||||
return false;
|
||||
|
||||
if (ChannelData.Length != other.ChannelData.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < ChannelData.Length; i++)
|
||||
{
|
||||
if (!Equals(ChannelData[i], other.ChannelData[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// takes an IndexCollection and can efficiently update index values
|
||||
private class IndexUpdateList
|
||||
{
|
||||
private readonly IList<int> _collectionToUpdate;
|
||||
private readonly Dictionary<int, List<int>> _indexPositions;
|
||||
|
||||
// create the list, presort the values and compute the start positions of each value
|
||||
public IndexUpdateList(IList<int> collectionToUpdate)
|
||||
{
|
||||
_collectionToUpdate = collectionToUpdate;
|
||||
_indexPositions = new Dictionary<int, List<int>>();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
for (var pos = 0; pos < _collectionToUpdate.Count; pos++)
|
||||
{
|
||||
var v = _collectionToUpdate[pos];
|
||||
if (_indexPositions.ContainsKey(v))
|
||||
_indexPositions[v].Add(pos);
|
||||
else
|
||||
_indexPositions.Add(v, new List<int> {pos});
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(int from, int to)
|
||||
{
|
||||
if (from == to || !_indexPositions.ContainsKey(from))
|
||||
return;
|
||||
|
||||
foreach (var pos in _indexPositions[from])
|
||||
_collectionToUpdate[pos] = to;
|
||||
|
||||
if (_indexPositions.ContainsKey(to))
|
||||
_indexPositions[to].AddRange(_indexPositions[from]);
|
||||
else
|
||||
_indexPositions.Add(to, _indexPositions[from]);
|
||||
|
||||
_indexPositions.Remove(from);
|
||||
}
|
||||
|
||||
// Pack all indices together starting from zero
|
||||
// E.g. [5, 5, 3, 5, 21, 3] -> [1, 1, 0, 1, 2, 0]
|
||||
// note that the order must be kept
|
||||
public void Pack()
|
||||
{
|
||||
if (_collectionToUpdate.Count == 0)
|
||||
return;
|
||||
|
||||
var sorted = new SortedSet<int>(_collectionToUpdate);
|
||||
|
||||
var newIndex = 0;
|
||||
foreach (var value in sorted)
|
||||
{
|
||||
foreach (var pos in _indexPositions[value])
|
||||
_collectionToUpdate[pos] = newIndex;
|
||||
|
||||
newIndex++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+40
@@ -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.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for accessing a mipmap chain.
|
||||
/// </summary>
|
||||
public sealed class MipmapChain : Collection<BitmapContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MipmapChain.
|
||||
/// </summary>
|
||||
public MipmapChain()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MipmapChain with the specified mipmap.
|
||||
/// </summary>
|
||||
/// <param name="bitmap"></param>
|
||||
public MipmapChain(BitmapContent bitmap)
|
||||
{
|
||||
Add(bitmap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new mipmap chain containing the specified bitmap.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">Bitmap used for the mipmap chain.</param>
|
||||
/// <returns>Resultant mipmap chain.</returns>
|
||||
public static implicit operator MipmapChain(BitmapContent bitmap)
|
||||
{
|
||||
return new MipmapChain(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -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.ObjectModel;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for maintaining a mipmap chain.
|
||||
/// </summary>
|
||||
public sealed class MipmapChainCollection : Collection<MipmapChain>
|
||||
{
|
||||
private readonly bool _fixedSize;
|
||||
|
||||
private const string CannotResizeError = "Cannot resize MipmapChainCollection. This type of texture has a fixed number of faces.";
|
||||
|
||||
internal MipmapChainCollection(int count, bool fixedSize)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
Add(new MipmapChain());
|
||||
|
||||
_fixedSize = fixedSize;
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
if (_fixedSize)
|
||||
throw new NotSupportedException(CannotResizeError);
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
if (_fixedSize)
|
||||
throw new NotSupportedException(CannotResizeError);
|
||||
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, MipmapChain item)
|
||||
{
|
||||
if (_fixedSize)
|
||||
throw new NotSupportedException(CannotResizeError);
|
||||
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for graphics types that define local coordinate systems.
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerDisplay("Node '{Name}'")]
|
||||
public class NodeContent : ContentItem
|
||||
{
|
||||
Matrix transform;
|
||||
NodeContent parent;
|
||||
NodeContentCollection children;
|
||||
AnimationContentDictionary animations;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the local Transform property, multiplied by the AbsoluteTransform of the parent.
|
||||
/// </summary>
|
||||
public Matrix AbsoluteTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (parent != null)
|
||||
return transform * parent.AbsoluteTransform;
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of animations belonging to this node.
|
||||
/// </summary>
|
||||
public AnimationContentDictionary Animations
|
||||
{
|
||||
get
|
||||
{
|
||||
return animations;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of the NodeContent object.
|
||||
/// </summary>
|
||||
public NodeContentCollection Children
|
||||
{
|
||||
get
|
||||
{
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this NodeContent object.
|
||||
/// </summary>
|
||||
public NodeContent Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
set
|
||||
{
|
||||
parent = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transform matrix of the scene.
|
||||
/// The transform matrix defines a local coordinate system for the content in addition to any children of this object.
|
||||
/// </summary>
|
||||
public Matrix Transform
|
||||
{
|
||||
get
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
set
|
||||
{
|
||||
transform = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of NodeContent.
|
||||
/// </summary>
|
||||
public NodeContent()
|
||||
{
|
||||
children = new NodeContentCollection(this);
|
||||
animations = new AnimationContentDictionary();
|
||||
Transform = Matrix.Identity;
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class NodeContentCollection : ChildCollection<NodeContent, NodeContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of NodeContentCollection.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent object of the child objects returned in the collection.</param>
|
||||
internal NodeContentCollection(NodeContent parent)
|
||||
: base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of a child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being retrieved.</param>
|
||||
/// <returns>The parent of the child object.</returns>
|
||||
protected override NodeContent GetParent(NodeContent child)
|
||||
{
|
||||
return child.Parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the value of the parent object of the specified child object.
|
||||
/// </summary>
|
||||
/// <param name="child">The child of the parent being modified.</param>
|
||||
/// <param name="parent">The new value for the parent object.</param>
|
||||
protected override void SetParent(NodeContent child, NodeContent parent)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
// 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.Utilities;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class PixelBitmapContent<T> : BitmapContent where T : struct, IEquatable<T>
|
||||
{
|
||||
internal T[][] _pixelData;
|
||||
|
||||
internal SurfaceFormat _format;
|
||||
|
||||
public PixelBitmapContent(int width, int height)
|
||||
{
|
||||
if (!TryGetFormat(out _format))
|
||||
throw new InvalidOperationException(string.Format("Color format \"{0}\" is not supported",typeof(T).ToString()));
|
||||
Height = height;
|
||||
Width = width;
|
||||
|
||||
_pixelData = new T[height][];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
_pixelData[y] = new T[width];
|
||||
|
||||
}
|
||||
|
||||
public override byte[] GetPixelData()
|
||||
{
|
||||
var formatSize = _format.GetSize();
|
||||
var dataSize = Width * Height * formatSize;
|
||||
var outputData = new byte[dataSize];
|
||||
|
||||
for (var x = 0; x < Height; x++)
|
||||
{
|
||||
var dataHandle = GCHandle.Alloc(_pixelData[x], GCHandleType.Pinned);
|
||||
var dataPtr = (IntPtr)(dataHandle.AddrOfPinnedObject().ToInt64());
|
||||
|
||||
Marshal.Copy(dataPtr, outputData, (formatSize * x * Width), (Width * formatSize));
|
||||
|
||||
dataHandle.Free();
|
||||
}
|
||||
|
||||
return outputData;
|
||||
}
|
||||
|
||||
public override void SetPixelData(byte[] sourceData)
|
||||
{
|
||||
var size = _format.GetSize();
|
||||
|
||||
for (var x = 0; x < Height; x++)
|
||||
{
|
||||
var dataHandle = GCHandle.Alloc(_pixelData[x], GCHandleType.Pinned);
|
||||
var dataPtr = (IntPtr)dataHandle.AddrOfPinnedObject().ToInt64();
|
||||
|
||||
Marshal.Copy(sourceData, (x * Width * size), dataPtr, Width * size);
|
||||
|
||||
dataHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public T[] GetRow(int y)
|
||||
{
|
||||
if (y < 0 || y >= Height)
|
||||
throw new ArgumentOutOfRangeException("y");
|
||||
|
||||
return _pixelData[y];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
if (typeof(T) == typeof(Color))
|
||||
format = SurfaceFormat.Color;
|
||||
else if (typeof(T) == typeof(Bgra4444))
|
||||
format = SurfaceFormat.Bgra4444;
|
||||
else if (typeof(T) == typeof(Bgra5551))
|
||||
format = SurfaceFormat.Bgra5551;
|
||||
else if (typeof(T) == typeof(Bgr565))
|
||||
format = SurfaceFormat.Bgr565;
|
||||
else if (typeof(T) == typeof(Vector4))
|
||||
format = SurfaceFormat.Vector4;
|
||||
else if (typeof(T) == typeof(Vector2))
|
||||
format = SurfaceFormat.Vector2;
|
||||
else if (typeof(T) == typeof(Single))
|
||||
format = SurfaceFormat.Single;
|
||||
else if (typeof(T) == typeof(byte))
|
||||
format = SurfaceFormat.Alpha8;
|
||||
else if (typeof(T) == typeof(Rgba64))
|
||||
format = SurfaceFormat.Rgba64;
|
||||
else if (typeof(T) == typeof(Rgba1010102))
|
||||
format = SurfaceFormat.Rgba1010102;
|
||||
else if (typeof(T) == typeof(Rg32))
|
||||
format = SurfaceFormat.Rg32;
|
||||
else if (typeof(T) == typeof(Byte4))
|
||||
format = SurfaceFormat.Color;
|
||||
else if (typeof(T) == typeof(NormalizedByte2))
|
||||
format = SurfaceFormat.NormalizedByte2;
|
||||
else if (typeof(T) == typeof(NormalizedByte4))
|
||||
format = SurfaceFormat.NormalizedByte4;
|
||||
else if (typeof(T) == typeof(HalfSingle))
|
||||
format = SurfaceFormat.HalfSingle;
|
||||
else if (typeof(T) == typeof(HalfVector2))
|
||||
format = SurfaceFormat.HalfVector2;
|
||||
else if (typeof(T) == typeof(HalfVector4))
|
||||
format = SurfaceFormat.HalfVector4;
|
||||
else
|
||||
{
|
||||
format = SurfaceFormat.Color;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public T GetPixel(int x, int y)
|
||||
{
|
||||
return _pixelData[y][x];
|
||||
}
|
||||
|
||||
public void SetPixel(int x, int y, T value)
|
||||
{
|
||||
_pixelData[y][x] = value;
|
||||
}
|
||||
|
||||
public void ReplaceColor(T originalColor, T newColor)
|
||||
{
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
for (var x = 0; x < Width; x++)
|
||||
{
|
||||
if (_pixelData[y][x].Equals(originalColor))
|
||||
_pixelData[y][x] = newColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
return false;
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (_format == sourceFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
SetPixelData(sourceBitmap.GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the source is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(sourceBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, this, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert from a Vector4 format
|
||||
var src = sourceBitmap as PixelBitmapContent<Vector4>;
|
||||
if (default(T) is IPackedVector)
|
||||
{
|
||||
Parallel.For(0, sourceRegion.Height, (y) =>
|
||||
{
|
||||
var pixel = default(T);
|
||||
var p = (IPackedVector)pixel;
|
||||
var row = src.GetRow(sourceRegion.Top + y);
|
||||
for (int x = 0; x < sourceRegion.Width; ++x)
|
||||
{
|
||||
p.PackFromVector4(row[sourceRegion.Left + x]);
|
||||
pixel = (T)p;
|
||||
SetPixel(destinationRegion.Left + x, destinationRegion.Top + y, pixel);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var converter = new Vector4Converter() as IVector4Converter<T>;
|
||||
// If no converter could be created, converting from this format is not supported
|
||||
if (converter == null)
|
||||
return false;
|
||||
|
||||
Parallel.For(0, sourceRegion.Height, (y) =>
|
||||
{
|
||||
var row = src.GetRow(sourceRegion.Top + y);
|
||||
for (int x = 0; x < sourceRegion.Width; ++x)
|
||||
{
|
||||
SetPixel(destinationRegion.Left + x, destinationRegion.Top + y, converter.FromVector4(row[sourceRegion.Left + x]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
return false;
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (_format == destinationFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
destinationBitmap.SetPixelData(GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the destination is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(destinationBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(this, sourceRegion, destinationBitmap, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to a Vector4 format
|
||||
var dest = destinationBitmap as PixelBitmapContent<Vector4>;
|
||||
if (default(T) is IPackedVector)
|
||||
{
|
||||
Parallel.For(0, sourceRegion.Height, (y) =>
|
||||
{
|
||||
var row = GetRow(sourceRegion.Top + y);
|
||||
for (int x = 0; x < sourceRegion.Width; ++x)
|
||||
dest.SetPixel(destinationRegion.Left + x, destinationRegion.Top + y, ((IPackedVector)row[sourceRegion.Left + x]).ToVector4());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var converter = new Vector4Converter() as IVector4Converter<T>;
|
||||
// If no converter could be created, converting from this format is not supported
|
||||
if (converter == null)
|
||||
return false;
|
||||
|
||||
Parallel.For(0, sourceRegion.Height, (y) =>
|
||||
{
|
||||
var row = GetRow(sourceRegion.Top + y);
|
||||
for (int x = 0; x < sourceRegion.Width; ++x)
|
||||
dest.SetPixel(destinationRegion.Left + x, destinationRegion.Top + y, converter.ToVector4(row[sourceRegion.Left + x]));
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// 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.ObjectModel;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a collection of vertex position values.
|
||||
/// </summary>
|
||||
public sealed class PositionCollection : Collection<Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of PositionCollection.
|
||||
/// </summary>
|
||||
public PositionCollection()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// 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.Graphics;
|
||||
using PVRTexLibNET;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public abstract class PvrtcBitmapContent : BitmapContent
|
||||
{
|
||||
internal byte[] _bitmapData;
|
||||
|
||||
public PvrtcBitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
int GetDataSize()
|
||||
{
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.RgbPvrtc2Bpp:
|
||||
case SurfaceFormat.RgbaPvrtc2Bpp:
|
||||
return (Math.Max(Width, 16) * Math.Max(Height, 8) * 2 + 7) / 8;
|
||||
|
||||
case SurfaceFormat.RgbPvrtc4Bpp:
|
||||
case SurfaceFormat.RgbaPvrtc4Bpp:
|
||||
return (Math.Max(Width, 8) * Math.Max(Height, 8) * 4 + 7) / 8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override byte[] GetPixelData()
|
||||
{
|
||||
if (_bitmapData == null)
|
||||
throw new InvalidOperationException("No data set on bitmap");
|
||||
var result = new byte[_bitmapData.Length];
|
||||
Buffer.BlockCopy(_bitmapData, 0, result, 0, _bitmapData.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetPixelData(byte[] sourceData)
|
||||
{
|
||||
var size = GetDataSize();
|
||||
if (sourceData.Length != size)
|
||||
throw new ArgumentException("Incorrect data size. Expected " + size + " bytes");
|
||||
if (_bitmapData == null || _bitmapData.Length != size)
|
||||
_bitmapData = new byte[size];
|
||||
Buffer.BlockCopy(sourceData, 0, _bitmapData, 0, size);
|
||||
}
|
||||
|
||||
protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat sourceFormat;
|
||||
if (!sourceBitmap.TryGetFormat(out sourceFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (format == sourceFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
SetPixelData(sourceBitmap.GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Destination region copy is not yet supported
|
||||
if (destinationRegion != new Rectangle(0, 0, Width, Height))
|
||||
return false;
|
||||
|
||||
// If the source is not Vector4 or requires resizing, send it through BitmapContent.Copy
|
||||
if (!(sourceBitmap is PixelBitmapContent<Vector4>) || sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapContent.Copy(sourceBitmap, sourceRegion, this, destinationRegion);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PixelFormat targetFormat;
|
||||
switch (format)
|
||||
{
|
||||
case SurfaceFormat.RgbPvrtc2Bpp:
|
||||
targetFormat = PixelFormat.PVRTCI_2bpp_RGB;
|
||||
break;
|
||||
case SurfaceFormat.RgbaPvrtc2Bpp:
|
||||
targetFormat = PixelFormat.PVRTCI_2bpp_RGBA;
|
||||
break;
|
||||
case SurfaceFormat.RgbPvrtc4Bpp:
|
||||
targetFormat = PixelFormat.PVRTCI_4bpp_RGB;
|
||||
break;
|
||||
case SurfaceFormat.RgbaPvrtc4Bpp:
|
||||
targetFormat = PixelFormat.PVRTCI_4bpp_RGBA;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the texture object in the PVR library
|
||||
var sourceData = sourceBitmap.GetPixelData();
|
||||
var rgba32F = (PixelFormat)0x2020202061626772; // static const PixelType PVRStandard32PixelType = PixelType('r', 'g', 'b', 'a', 32, 32, 32, 32);
|
||||
using (var pvrTexture = PVRTexture.CreateTexture(sourceData, (uint)sourceBitmap.Width, (uint)sourceBitmap.Height, 1,
|
||||
rgba32F, true, VariableType.Float, ColourSpace.lRGB))
|
||||
{
|
||||
// Resize the bitmap if needed
|
||||
if ((sourceBitmap.Width != Width) || (sourceBitmap.Height != Height))
|
||||
pvrTexture.Resize((uint)Width, (uint)Height, 1, ResizeMode.Cubic);
|
||||
// On Linux, anything less than CompressorQuality.PVRTCHigh crashes in libpthread.so at the end of compression
|
||||
pvrTexture.Transcode(targetFormat, VariableType.UnsignedByte, ColourSpace.lRGB, CompressorQuality.PVRTCHigh);
|
||||
var texDataSize = pvrTexture.GetTextureDataSize(0);
|
||||
var texData = new byte[texDataSize];
|
||||
pvrTexture.GetTextureData(texData, texDataSize);
|
||||
SetPixelData(texData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryCopyTo(BitmapContent destinationBitmap, Rectangle sourceRegion, Rectangle destinationRegion)
|
||||
{
|
||||
SurfaceFormat destinationFormat;
|
||||
if (!destinationBitmap.TryGetFormat(out destinationFormat))
|
||||
return false;
|
||||
|
||||
SurfaceFormat format;
|
||||
TryGetFormat(out format);
|
||||
|
||||
// A shortcut for copying the entire bitmap to another bitmap of the same type and format
|
||||
if (format == destinationFormat && (sourceRegion == new Rectangle(0, 0, Width, Height)) && sourceRegion == destinationRegion)
|
||||
{
|
||||
destinationBitmap.SetPixelData(GetPixelData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// No other support for copying from a PVR texture yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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 Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class PvrtcRgb2BitmapContent : PvrtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of PvrtcRgb2BitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public PvrtcRgb2BitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbPvrtc2Bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "PVRTC RGB 2bpp " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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 Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class PvrtcRgb4BitmapContent : PvrtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of PvrtcRgb4BitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public PvrtcRgb4BitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbPvrtc4Bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "PVRTC RGB 4bpp " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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 Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class PvrtcRgba2BitmapContent : PvrtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of PvrtcRgba2BitBitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public PvrtcRgba2BitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbaPvrtc2Bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "PVRTC RGBA 2bpp " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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 Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class PvrtcRgba4BitmapContent : PvrtcBitmapContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of PvrtcRgba4BitBitmapContent with the specified width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width in pixels of the bitmap.</param>
|
||||
/// <param name="height">The height in pixels of the bitmap.</param>
|
||||
public PvrtcRgba4BitmapContent(int width, int height)
|
||||
: base(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding GPU texture format for the specified bitmap type.
|
||||
/// </summary>
|
||||
/// <param name="format">Format being retrieved.</param>
|
||||
/// <returns>The GPU texture format of the bitmap type.</returns>
|
||||
public override bool TryGetFormat(out SurfaceFormat format)
|
||||
{
|
||||
format = SurfaceFormat.RgbaPvrtc4Bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string description of the bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Description of the bitmap.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "PVRTC RGBA 4bpp " + Width + "x" + Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class SkinnedMaterialContent : MaterialContent
|
||||
{
|
||||
public const string AlphaKey = "Alpha";
|
||||
public const string DiffuseColorKey = "DiffuseColor";
|
||||
public const string EmissiveColorKey = "EmissiveColor";
|
||||
public const string SpecularColorKey = "SpecularColor";
|
||||
public const string SpecularPowerKey = "SpecularPower";
|
||||
public const string TextureKey = "Texture";
|
||||
public const string WeightsPerVertexKey = "WeightsPerVertex";
|
||||
|
||||
public float? Alpha
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(AlphaKey); }
|
||||
set { SetProperty(AlphaKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? DiffuseColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(DiffuseColorKey); }
|
||||
set { SetProperty(DiffuseColorKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? EmissiveColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(EmissiveColorKey); }
|
||||
set { SetProperty(EmissiveColorKey, value); }
|
||||
}
|
||||
|
||||
public Vector3? SpecularColor
|
||||
{
|
||||
get { return GetValueTypeProperty<Vector3>(SpecularColorKey); }
|
||||
set { SetProperty(SpecularColorKey, value); }
|
||||
}
|
||||
|
||||
public float? SpecularPower
|
||||
{
|
||||
get { return GetValueTypeProperty<float>(SpecularPowerKey); }
|
||||
set { SetProperty(SpecularPowerKey, value); }
|
||||
}
|
||||
|
||||
public ExternalReference<TextureContent> Texture
|
||||
{
|
||||
get { return GetTexture(TextureKey); }
|
||||
set { SetTexture(TextureKey, value); }
|
||||
}
|
||||
|
||||
public int? WeightsPerVertex
|
||||
{
|
||||
get { return GetValueTypeProperty<int>(WeightsPerVertexKey); }
|
||||
set { SetProperty(WeightsPerVertexKey, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// 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.Graphics;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for all texture objects.
|
||||
/// </summary>
|
||||
public abstract class SpriteFontDescriptionContent : ContentItem, IDisposable
|
||||
{
|
||||
MipmapChainCollection faces;
|
||||
internal Bitmap _bitmap;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of image faces that hold a single mipmap chain for a regular 2D texture, six chains for a cube map, or an arbitrary number for volume and array textures.
|
||||
/// </summary>
|
||||
public MipmapChainCollection Faces
|
||||
{
|
||||
get
|
||||
{
|
||||
return faces;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of TextureContent with the specified face collection.
|
||||
/// </summary>
|
||||
/// <param name="faces">Mipmap chain containing the face collection.</param>
|
||||
protected TextureContent(MipmapChainCollection faces)
|
||||
{
|
||||
this.faces = faces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts all bitmaps for this texture to a different format.
|
||||
/// </summary>
|
||||
/// <param name="newBitmapType">Type being converted to. The new type must be a subclass of BitmapContent, such as PixelBitmapContent or DxtBitmapContent.</param>
|
||||
public void ConvertBitmapType(Type newBitmapType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a full set of mipmaps for the texture.
|
||||
/// </summary>
|
||||
/// <param name="overwriteExistingMipmaps">true if the existing mipmap set is replaced with the new set; false otherwise.</param>
|
||||
public virtual void GenerateMipmaps(bool overwriteExistingMipmaps)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that all contents of this texture are present, correct and match the capabilities of the device.
|
||||
/// </summary>
|
||||
/// <param name="targetProfile">The profile identifier that defines the capabilities of the device.</param>
|
||||
public abstract void Validate(Nullable<GraphicsProfile> targetProfile);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (_bitmap != null)
|
||||
{
|
||||
_bitmap.Dispose();
|
||||
_bitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class Texture2DContent : TextureContent
|
||||
{
|
||||
public MipmapChain Mipmaps
|
||||
{
|
||||
get { return Faces[0]; }
|
||||
set { Faces[0] = value; }
|
||||
}
|
||||
|
||||
public Texture2DContent() :
|
||||
base(new MipmapChainCollection(1, true))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Validate(GraphicsProfile? targetProf)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class Texture3DContent : TextureContent
|
||||
{
|
||||
public Texture3DContent() :
|
||||
base(new MipmapChainCollection(0, false))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Validate(GraphicsProfile? targetProf)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void GenerateMipmaps(bool overwriteExistingMipmaps)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for all texture objects.
|
||||
/// </summary>
|
||||
public abstract class TextureContent : ContentItem
|
||||
{
|
||||
MipmapChainCollection faces;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of image faces that hold a single mipmap chain for a regular 2D texture, six chains for a cube map, or an arbitrary number for volume and array textures.
|
||||
/// </summary>
|
||||
public MipmapChainCollection Faces
|
||||
{
|
||||
get
|
||||
{
|
||||
return faces;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of TextureContent with the specified face collection.
|
||||
/// </summary>
|
||||
/// <param name="faces">Mipmap chain containing the face collection.</param>
|
||||
protected TextureContent(MipmapChainCollection faces)
|
||||
{
|
||||
this.faces = faces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts all bitmaps for this texture to a different format.
|
||||
/// </summary>
|
||||
/// <param name="newBitmapType">Type being converted to. The new type must be a subclass of BitmapContent, such as PixelBitmapContent or DxtBitmapContent.</param>
|
||||
public void ConvertBitmapType(Type newBitmapType)
|
||||
{
|
||||
if (newBitmapType == null)
|
||||
throw new ArgumentNullException("newBitmapType");
|
||||
|
||||
if (!newBitmapType.IsSubclassOf(typeof (BitmapContent)))
|
||||
throw new ArgumentException(string.Format("Type '{0}' is not a subclass of BitmapContent.", newBitmapType));
|
||||
|
||||
if (newBitmapType.IsAbstract)
|
||||
throw new ArgumentException(string.Format("Type '{0}' is abstract and cannot be allocated.", newBitmapType));
|
||||
|
||||
if (newBitmapType.ContainsGenericParameters)
|
||||
throw new ArgumentException(string.Format("Type '{0}' contains generic parameters and cannot be allocated.", newBitmapType));
|
||||
|
||||
if (newBitmapType.GetConstructor(new Type[2] {typeof (int), typeof (int)}) == null)
|
||||
throw new ArgumentException(string.Format("Type '{0} does not have a constructor with signature (int, int) and cannot be allocated.",
|
||||
newBitmapType));
|
||||
|
||||
foreach (var mipChain in faces)
|
||||
{
|
||||
for (var i = 0; i < mipChain.Count; i++)
|
||||
{
|
||||
var src = mipChain[i];
|
||||
if (src.GetType() != newBitmapType)
|
||||
{
|
||||
var dst = (BitmapContent)Activator.CreateInstance(newBitmapType, new object[] { src.Width,src.Height });
|
||||
BitmapContent.Copy(src, dst);
|
||||
mipChain[i] = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a full set of mipmaps for the texture.
|
||||
/// </summary>
|
||||
/// <param name="overwriteExistingMipmaps">true if the existing mipmap set is replaced with the new set; false otherwise.</param>
|
||||
public virtual void GenerateMipmaps(bool overwriteExistingMipmaps)
|
||||
{
|
||||
// If we already have mipmaps and we're not supposed to overwrite
|
||||
// them then return without any generation.
|
||||
if (!overwriteExistingMipmaps && faces.Any(f => f.Count > 1))
|
||||
return;
|
||||
|
||||
// Generate the mips for each face.
|
||||
foreach (var face in faces)
|
||||
{
|
||||
// Remove any existing mipmaps.
|
||||
var faceBitmap = face[0];
|
||||
face.Clear();
|
||||
face.Add(faceBitmap);
|
||||
var faceType = faceBitmap.GetType();
|
||||
int width = faceBitmap.Width;
|
||||
int height = faceBitmap.Height;
|
||||
while (width > 1 || height > 1)
|
||||
{
|
||||
if (width > 1)
|
||||
width /= 2;
|
||||
if (height > 1)
|
||||
height /= 2;
|
||||
|
||||
var mip = (BitmapContent)Activator.CreateInstance(faceType, new object[] { width, height });
|
||||
BitmapContent.Copy(faceBitmap, mip);
|
||||
face.Add(mip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that all contents of this texture are present, correct and match the capabilities of the device.
|
||||
/// </summary>
|
||||
/// <param name="targetProfile">The profile identifier that defines the capabilities of the device.</param>
|
||||
public abstract void Validate(GraphicsProfile? targetProfile);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// 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.Graphics;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public class TextureCubeContent : TextureContent
|
||||
{
|
||||
public TextureCubeContent() :
|
||||
base(new MipmapChainCollection(6, true))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Validate(GraphicsProfile? targetProf)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// 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.Processors;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public abstract class TextureProfile
|
||||
{
|
||||
private static readonly LoadedTypeCollection<TextureProfile> _profiles = new LoadedTypeCollection<TextureProfile>();
|
||||
|
||||
/// <summary>
|
||||
/// Find the profile for this target platform.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform target for textures.</param>
|
||||
/// <returns></returns>
|
||||
public static TextureProfile ForPlatform(TargetPlatform platform)
|
||||
{
|
||||
var profile = _profiles.FirstOrDefault(h => h.Supports(platform));
|
||||
if (profile != null)
|
||||
return profile;
|
||||
|
||||
throw new PipelineException("There is no supported texture profile for the '" + platform + "' platform!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this profile supports texture processing for this platform.
|
||||
/// </summary>
|
||||
public abstract bool Supports(TargetPlatform platform);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the texture format will require power-of-two dimensions and/or equal width and height.
|
||||
/// </summary>
|
||||
/// <param name="context">The processor context.</param>
|
||||
/// <param name="format">The desired texture format.</param>
|
||||
/// <param name="requiresPowerOfTwo">True if the texture format requires power-of-two dimensions.</param>
|
||||
/// <param name="requiresSquare">True if the texture format requires equal width and height.</param>
|
||||
/// <returns>True if the texture format requires power-of-two dimensions.</returns>
|
||||
public abstract void Requirements(ContentProcessorContext context, TextureProcessorOutputFormat format, out bool requiresPowerOfTwo, out bool requiresSquare);
|
||||
|
||||
/// <summary>
|
||||
/// Performs conversion of the texture content to the correct format.
|
||||
/// </summary>
|
||||
/// <param name="context">The processor context.</param>
|
||||
/// <param name="content">The content to be compressed.</param>
|
||||
/// <param name="format">The user requested format for compression.</param>
|
||||
/// <param name="isSpriteFont">If the texture has represents a sprite font, i.e. is greyscale and has sharp black/white contrast.</param>
|
||||
public void ConvertTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool isSpriteFont)
|
||||
{
|
||||
// We do nothing in this case.
|
||||
if (format == TextureProcessorOutputFormat.NoChange)
|
||||
return;
|
||||
|
||||
// If this is color just make sure the format is right and return it.
|
||||
if (format == TextureProcessorOutputFormat.Color)
|
||||
{
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Color>));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle this common compression format.
|
||||
if (format == TextureProcessorOutputFormat.Color16Bit)
|
||||
{
|
||||
GraphicsUtil.CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// All other formats require platform specific choices.
|
||||
PlatformCompressTexture(context, content, format, isSpriteFont);
|
||||
}
|
||||
catch (EntryPointNotFoundException ex)
|
||||
{
|
||||
context.Logger.LogImportantMessage("Could not find the entry point to compress the texture. " + ex.ToString());
|
||||
throw ex;
|
||||
}
|
||||
catch (DllNotFoundException ex)
|
||||
{
|
||||
context.Logger.LogImportantMessage("Could not compress texture. Required shared lib is missing. " + ex.ToString());
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Logger.LogImportantMessage("Could not convert texture. " + ex.ToString());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void PlatformCompressTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool isSpriteFont);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a collection of named references to texture files.
|
||||
/// </summary>
|
||||
public sealed class TextureReferenceDictionary : NamedValueDictionary<ExternalReference<TextureContent>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of TextureReferenceDictionary.
|
||||
/// </summary>
|
||||
public TextureReferenceDictionary()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods and properties for maintaining a vertex channel.
|
||||
/// A vertex channel is a list of arbitrary data with one value for each vertex. Channels are stored inside a GeometryContent and identified by name.
|
||||
/// </summary>
|
||||
public abstract class VertexChannel : IList, ICollection, IEnumerable
|
||||
{
|
||||
string name;
|
||||
|
||||
/// <summary>
|
||||
/// Allows overriding classes to implement the list, and for properties/methods in this class to access it.
|
||||
/// </summary>
|
||||
internal abstract IList Items
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements in the vertex channel
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return Items.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of data contained in this channel.
|
||||
/// </summary>
|
||||
public abstract Type ElementType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element at the specified index.
|
||||
/// </summary>
|
||||
public Object this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return Items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
Items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the vertex channel.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return name;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether access to the collection is synchronized (thread safe).
|
||||
/// </summary>
|
||||
bool System.Collections.ICollection.IsSynchronized
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an object that can be used to synchronize access to the collection.
|
||||
/// </summary>
|
||||
Object System.Collections.ICollection.SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this list has a fixed size.
|
||||
/// </summary>
|
||||
bool System.Collections.IList.IsFixedSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this object is read-only.
|
||||
/// </summary>
|
||||
bool System.Collections.IList.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of VertexChannel.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the channel.</param>
|
||||
internal VertexChannel(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified element is in the channel.
|
||||
/// </summary>
|
||||
/// <param name="value">Element being searched for.</param>
|
||||
/// <returns>true if the element is present; false otherwise.</returns>
|
||||
public bool Contains(Object value)
|
||||
{
|
||||
return Items.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the channel to an array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">Array that will receive the copied channel elements.</param>
|
||||
/// <param name="index">Starting index for copy operation.</param>
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)Items).CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator interface for reading channel content.
|
||||
/// </summary>
|
||||
/// <returns>Enumeration of the channel content.</returns>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return Items.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the specified item.
|
||||
/// </summary>
|
||||
/// <param name="value">Item whose index is to be retrieved.</param>
|
||||
/// <returns>Index of specified item.</returns>
|
||||
public int IndexOf(Object value)
|
||||
{
|
||||
return Items.IndexOf(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads channel content and automatically converts it to the specified vector format.
|
||||
/// </summary>
|
||||
/// <typeparam name="TargetType">Target vector format of the converted data.</typeparam>
|
||||
/// <returns>The converted data.</returns>
|
||||
public abstract IEnumerable<TargetType> ReadConvertedContent<TargetType>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new element to the end of the collection.
|
||||
/// </summary>
|
||||
/// <param name="value">The element to add.</param>
|
||||
/// <returns>Index of the element.</returns>
|
||||
int IList.Add(Object value)
|
||||
{
|
||||
return Items.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all elements from the collection.
|
||||
/// </summary>
|
||||
void IList.Clear()
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an element into the collection at the specified position.
|
||||
/// </summary>
|
||||
/// <param name="index">Index at which to insert the element.</param>
|
||||
/// <param name="value">The element to insert.</param>
|
||||
void IList.Insert(int index, Object value)
|
||||
{
|
||||
Items.Insert(index, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the range of values from the enumerable into the channel.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index at which the new elements should be inserted.</param>
|
||||
/// <param name="data">The data to insert into the channel.</param>
|
||||
internal abstract void InsertRange(int index, IEnumerable data);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specified element from the collection.
|
||||
/// </summary>
|
||||
/// <param name="value">The element to remove.</param>
|
||||
void IList.Remove(Object value)
|
||||
{
|
||||
Items.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element at the specified index position.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the element to remove.</param>
|
||||
void IList.RemoveAt(int index)
|
||||
{
|
||||
Items.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of values from the channel.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based starting index of the range of elements to remove.</param>
|
||||
/// <param name="count"> The number of elements to remove.</param>
|
||||
internal abstract void RemoveRange(int index, int count);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user