(ded4a3e0a) v0.9.0.7

This commit is contained in:
Joonas Rikkonen
2019-06-25 16:00:44 +03:00
parent e5ae622c77
commit 4a51db77b5
1777 changed files with 421528 additions and 917 deletions
@@ -0,0 +1,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); }
}
}
}
@@ -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();
}
}
}
@@ -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()
{
}
}
}
@@ -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();
}
}
}
@@ -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()
{
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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); }
}
}
}
@@ -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");
}
}
}
@@ -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()
{
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
}
}
@@ -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); }
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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; }
}
}
@@ -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); }
}
}
}
@@ -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); }
}
}
}
@@ -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;
}
}
}
@@ -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");
}
}
}
@@ -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;
}
}
}
}
}
@@ -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));
}
}
@@ -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;
}
}
@@ -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++;
}
}
}
}
@@ -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);
}
}
}
}
@@ -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; }
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
}
@@ -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,
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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]);
}
}
}
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -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>();
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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()
{
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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); }
}
}
}
@@ -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;
}
}
}
}
@@ -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();
}
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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()
{
}
}
}
@@ -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);
}
}
@@ -0,0 +1,378 @@
// 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;
using System.Collections;
using System.Reflection;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
/// <summary>
/// Provides methods and properties for managing a list of vertex data channels.
/// </summary>
public sealed class VertexChannelCollection : IList<VertexChannel>, ICollection<VertexChannel>, IEnumerable<VertexChannel>, IEnumerable
{
List<VertexChannel> channels;
VertexContent vertexContent;
/// <summary>
/// Gets the number of vertex channels in the collection.
/// </summary>
public int Count
{
get
{
return channels.Count;
}
}
/// <summary>
/// Gets or sets the vertex channel at the specified index position.
/// </summary>
public VertexChannel this[int index]
{
get
{
return channels[index];
}
set
{
channels[index] = value;
}
}
/// <summary>
/// Gets or sets the vertex channel with the specified name.
/// </summary>
public VertexChannel this[string name]
{
get
{
var index = IndexOf(name);
if (index < 0)
throw new ArgumentException("name");
return channels[index];
}
set
{
var index = IndexOf(name);
if (index < 0)
throw new ArgumentException("name");
channels[index] = value;
}
}
/// <summary>
/// Determines whether the collection is read-only.
/// </summary>
bool ICollection<VertexChannel>.IsReadOnly
{
get
{
return false;
}
}
/// <summary>
/// Creates an instance of VertexChannelCollection.
/// </summary>
/// <param name="vertexContent">The VertexContent object that owns this collection.</param>
internal VertexChannelCollection(VertexContent vertexContent)
{
this.vertexContent = vertexContent;
channels = new List<VertexChannel>();
_insertOverload = GetType().GetMethods().First(m => m.Name == "Insert" && m.IsGenericMethodDefinition);
}
/// <summary>
/// Adds a new vertex channel to the end of the collection.
/// </summary>
/// <typeparam name="ElementType">Type of the channel.</typeparam>
/// <param name="name">Name of the new channel.</param>
/// <param name="channelData">Initial data for the new channel. If null, the channel is filled with the default value for that type.</param>
/// <returns>The newly added vertex channel.</returns>
public VertexChannel<ElementType> Add<ElementType>(string name, IEnumerable<ElementType> channelData)
{
return Insert(channels.Count, name, channelData);
}
/// <summary>
/// Adds a new vertex channel to the end of the collection.
/// </summary>
/// <param name="name">Name of the new channel.</param>
/// <param name="elementType">Type of data to be contained in the new channel.</param>
/// <param name="channelData">Initial data for the new channel. If null, the channel is filled with the default value for that type.</param>
/// <returns>The newly added vertex channel.</returns>
public VertexChannel Add(string name, Type elementType, IEnumerable channelData)
{
return Insert(channels.Count, name, elementType, channelData);
}
/// <summary>
/// Removes all vertex channels from the collection.
/// </summary>
public void Clear()
{
channels.Clear();
}
/// <summary>
/// Determines whether the collection contains the specified vertex channel.
/// </summary>
/// <param name="name">Name of the channel being searched for.</param>
/// <returns>true if the channel was found; false otherwise.</returns>
public bool Contains(string name)
{
return channels.Exists(c => { return c.Name == name; });
}
/// <summary>
/// Determines whether the collection contains the specified vertex channel.
/// </summary>
/// <param name="item">The channel being searched for.</param>
/// <returns>true if the channel was found; false otherwise.</returns>
public bool Contains(VertexChannel item)
{
return channels.Contains(item);
}
/// <summary>
/// Converts the channel, at the specified index, to another vector format.
/// </summary>
/// <typeparam name="TargetType">Type of the target format. Can be one of the following: Single, Vector2, Vector3, Vector4, IPackedVector</typeparam>
/// <param name="index">Index of the channel to be converted.</param>
/// <returns>New channel in the specified format.</returns>
public VertexChannel<TargetType> ConvertChannelContent<TargetType>(int index)
{
if (index < 0 || index >= channels.Count)
throw new ArgumentOutOfRangeException("index");
// Get the channel at that index
var channel = this[index];
// Remove it because we cannot add a new channel with the same name
RemoveAt(index);
VertexChannel<TargetType> result = null;
try
{
// Insert a new converted channel at the same index
result = Insert(index, channel.Name, channel.ReadConvertedContent<TargetType>());
}
catch
{
// If anything went wrong, put the old channel back...
channels.Insert(index, channel);
// ...before throwing the exception again
throw;
}
// Return the new converted channel
return result;
}
/// <summary>
/// Converts the channel, specified by name to another vector format.
/// </summary>
/// <typeparam name="TargetType">Type of the target format. Can be one of the following: Single, Vector2, Vector3, Vector4, IPackedVector</typeparam>
/// <param name="name">Name of the channel to be converted.</param>
/// <returns>New channel in the specified format.</returns>
public VertexChannel<TargetType> ConvertChannelContent<TargetType>(string name)
{
var index = IndexOf(name);
if (index < 0)
throw new ArgumentException("name");
return ConvertChannelContent<TargetType>(index);
}
/// <summary>
/// Gets the vertex channel with the specified index and content type.
/// </summary>
/// <typeparam name="T">Type of a vertex channel.</typeparam>
/// <param name="index">Index of a vertex channel.</param>
/// <returns>The vertex channel.</returns>
public VertexChannel<T> Get<T>(int index)
{
if (index < 0 || index >= channels.Count)
throw new ArgumentOutOfRangeException("index");
var channel = this[index];
// Make sure the channel type is as expected
if (channel.ElementType != typeof(T))
throw new InvalidOperationException("Mismatched channel type");
return (VertexChannel<T>)channel;
}
/// <summary>
/// Gets the vertex channel with the specified name and content type.
/// </summary>
/// <typeparam name="T">Type of the vertex channel.</typeparam>
/// <param name="name">Name of a vertex channel.</param>
/// <returns>The vertex channel.</returns>
public VertexChannel<T> Get<T>(string name)
{
var index = IndexOf(name);
if (index < 0)
throw new ArgumentException("name");
return Get<T>(index);
}
/// <summary>
/// Gets an enumerator that iterates through the vertex channels of a collection.
/// </summary>
/// <returns>Enumerator for the collection.</returns>
public IEnumerator<VertexChannel> GetEnumerator()
{
return channels.GetEnumerator();
}
/// <summary>
/// Determines the index of a vertex channel with the specified name.
/// </summary>
/// <param name="name">Name of the vertex channel being searched for.</param>
/// <returns>Index of the vertex channel.</returns>
public int IndexOf(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
return channels.FindIndex((v) => v.Name == name);
}
/// <summary>
/// Determines the index of the specified vertex channel.
/// </summary>
/// <param name="item">Vertex channel being searched for.</param>
/// <returns>Index of the vertex channel.</returns>
public int IndexOf(VertexChannel item)
{
if (item == null)
throw new ArgumentNullException("item");
return channels.IndexOf(item);
}
/// <summary>
/// Inserts a new vertex channel at the specified position.
/// </summary>
/// <typeparam name="ElementType">Type of the new channel.</typeparam>
/// <param name="index">Index for channel insertion.</param>
/// <param name="name">Name of the new channel.</param>
/// <param name="channelData">The new channel.</param>
/// <returns>The inserted vertex channel.</returns>
public VertexChannel<ElementType> Insert<ElementType>(int index, string name, IEnumerable<ElementType> channelData)
{
if ((index < 0) || (index > channels.Count))
throw new ArgumentOutOfRangeException("index");
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
// Don't insert a channel with the same name
if (IndexOf(name) >= 0)
throw new ArgumentException("Vertex channel with name " + name + " already exists");
var channel = new VertexChannel<ElementType>(name);
if (channelData != null)
{
// Insert the values from the enumerable into the channel
channel.InsertRange(0, channelData);
// Make sure we have the right number of vertices
if (channel.Count != vertexContent.VertexCount)
throw new ArgumentOutOfRangeException("channelData");
}
else
{
// Insert enough default values to fill the channel
channel.InsertRange(0, new ElementType[vertexContent.VertexCount]);
}
channels.Insert(index, channel);
return channel;
}
// this reference the above Insert method and is initialized in the constructor
private readonly MethodInfo _insertOverload;
/// <summary>
/// Inserts a new vertex channel at the specified position.
/// </summary>
/// <param name="index">Index for channel insertion.</param>
/// <param name="name">Name of the new channel.</param>
/// <param name="elementType">Type of the new channel.</param>
/// <param name="channelData">Initial data for the new channel. If null, it is filled with the default value.</param>
/// <returns>The inserted vertex channel.</returns>
public VertexChannel Insert(int index, string name, Type elementType, IEnumerable channelData)
{
// Call the generic version of this method
return (VertexChannel) _insertOverload.MakeGenericMethod(elementType).Invoke(this, new object[] { index, name, channelData });
}
/// <summary>
/// Removes the specified vertex channel from the collection.
/// </summary>
/// <param name="name">Name of the vertex channel being removed.</param>
/// <returns>true if the channel was removed; false otherwise.</returns>
public bool Remove(string name)
{
var index = IndexOf(name);
if (index >= 0)
{
channels.RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// Removes the specified vertex channel from the collection.
/// </summary>
/// <param name="item">The vertex channel being removed.</param>
/// <returns>true if the channel was removed; false otherwise.</returns>
public bool Remove(VertexChannel item)
{
return channels.Remove(item);
}
/// <summary>
/// Removes the vertex channel at the specified index position.
/// </summary>
/// <param name="index">Index of the vertex channel being removed.</param>
public void RemoveAt(int index)
{
channels.RemoveAt(index);
}
/// <summary>
/// Adds a new vertex channel to the collection.
/// </summary>
/// <param name="item">Vertex channel to be added.</param>
void ICollection<VertexChannel>.Add(VertexChannel item)
{
channels.Add(item);
}
/// <summary>
/// Copies the elements of the collection to an array, starting at the specified index.
/// </summary>
/// <param name="array">The destination array.</param>
/// <param name="arrayIndex">The index at which to begin copying elements.</param>
void ICollection<VertexChannel>.CopyTo(VertexChannel[] array, int arrayIndex)
{
channels.CopyTo(array, arrayIndex);
}
/// <summary>
/// Inserts an item at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="item">The item to insert.</param>
void IList<VertexChannel>.Insert(int index, VertexChannel item)
{
channels.Insert(index, item);
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return channels.GetEnumerator();
}
}
}
@@ -0,0 +1,241 @@
// 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.Design;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
/// <summary>
/// Provides methods and properties for maintaining a vertex channel.
/// This is a generic implementation of VertexChannel and, therefore, can handle strongly typed content data.
/// </summary>
public sealed class VertexChannel<T> : VertexChannel, IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
List<T> items;
/// <summary>
/// Gets the strongly-typed list for the base class to access.
/// </summary>
internal override IList Items
{
get
{
return items;
}
}
/// <summary>
/// Gets the type of data contained in this channel.
/// </summary>
public override Type ElementType
{
get
{
return typeof(T);
}
}
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
public new T this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
}
}
/// <summary>
/// true if this object is read-only; false otherwise.
/// </summary>
bool ICollection<T>.IsReadOnly
{
get
{
return false;
}
}
/// <summary>
/// Creates an instance of VertexChannel.
/// </summary>
/// <param name="name">Name of the channel.</param>
internal VertexChannel(string name)
: base(name)
{
items = new List<T>();
}
static VertexChannel()
{
// Some platforms (such as Windows Store) don't support TypeConverter, which
// is normally referenced with an attribute on the target type. To keep them
// out of the main assembly, they are registered here before their use.
//TypeDescriptor.AddAttributes(typeof(Single), new TypeConverterAttribute(typeof(SingleTypeConverter)));
TypeDescriptor.AddAttributes(typeof(Vector2), new TypeConverterAttribute(typeof(Vector2TypeConverter)));
TypeDescriptor.AddAttributes(typeof(Vector3), new TypeConverterAttribute(typeof(Vector3TypeConverter)));
TypeDescriptor.AddAttributes(typeof(Vector4), new TypeConverterAttribute(typeof(Vector4TypeConverter)));
//TypeDescriptor.AddAttributes(typeof(IPackedVector), new TypeConverterAttribute(typeof(PackedVectorTypeConverter)));
}
/// <summary>
/// Determines whether the specified element is in the channel.
/// </summary>
/// <param name="item">Element being searched for.</param>
/// <returns>true if the element is present; false otherwise.</returns>
public bool Contains(T item)
{
return items.Contains(item);
}
/// <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="arrayIndex">Starting index for copy operation.</param>
public void CopyTo(T[] array, int arrayIndex)
{
items.CopyTo(array, arrayIndex);
}
/// <summary>
/// Gets an enumerator interface for reading channel content.
/// </summary>
/// <returns>Enumeration of the channel content.</returns>
public new IEnumerator<T> GetEnumerator()
{
return items.GetEnumerator();
}
/// <summary>
/// Gets the index of the specified item.
/// </summary>
/// <param name="item">Item whose index is to be retrieved.</param>
/// <returns>Index of specified item.</returns>
public int IndexOf(T item)
{
return items.IndexOf(item);
}
/// <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 override void InsertRange(int index, IEnumerable data)
{
if ((index < 0) || (index > items.Count))
throw new ArgumentOutOfRangeException("index");
if (data == null)
throw new ArgumentNullException("data");
if (!(data is IEnumerable<T>))
throw new ArgumentException("data");
items.InsertRange(index, (IEnumerable<T>)data);
}
/// <summary>
/// Reads channel content and automatically converts it to the specified vector format.
/// </summary>
/// <typeparam name="TargetType">Target vector format for the converted channel data.</typeparam>
/// <returns>The converted channel data.</returns>
public override IEnumerable<TargetType> ReadConvertedContent<TargetType>()
{
if (typeof(TargetType).IsAssignableFrom(typeof(T)))
return items.Cast<TargetType>();
return Convert<TargetType>(items);
}
private static IEnumerable<TargetType> Convert<TargetType>(IEnumerable<T> items)
{
// The following formats are supported:
// - Single
// - Vector2 Structure
// - Vector3 Structure
// - Vector4 Structure
// - Any implementation of IPackedVector Interface.
var converter = TypeDescriptor.GetConverter(typeof(T));
if (!converter.CanConvertTo(typeof(TargetType)))
{
// If you got this exception, check out the static constructor above
// to make sure your type is registered.
throw new NotImplementedException(
string.Format("TypeConverter for {0} -> {1} is not implemented.",
typeof(T).Name, typeof(TargetType).Name));
}
foreach (var item in items)
yield return (TargetType)converter.ConvertTo(item, typeof(TargetType));
}
/// <summary>
/// Adds a new element to the end of the collection.
/// </summary>
/// <param name="value">The element to add.</param>
void ICollection<T>.Add(T value)
{
((ICollection<T>)Items).Add(value);
}
/// <summary>
/// Removes all elements from the collection.
/// </summary>
void ICollection<T>.Clear()
{
Items.Clear();
}
/// <summary>
/// Removes a specified element from the collection.
/// </summary>
/// <param name="value">The element to remove.</param>
/// <returns>true if the channel was removed; false otherwise.</returns>
bool ICollection<T>.Remove(T value)
{
return ((ICollection<T>)Items).Remove(value);
}
/// <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<T>.Insert(int index, T value)
{
Items.Insert(index, value);
}
/// <summary>
/// Removes the element at the specified index position.
/// </summary>
/// <param name="index">Index of the element to remove.</param>
void IList<T>.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 override void RemoveRange(int index, int count)
{
items.RemoveRange(index, count);
}
}
}
@@ -0,0 +1,193 @@
// 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.Globalization;
using Microsoft.Xna.Framework.Graphics;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
/// <summary>
/// Provides properties for managing a collection of vertex channel names.
/// </summary>
public static class VertexChannelNames
{
/// <summary>
/// A lookup for the TryDecodeUsage method.
/// </summary>
static Dictionary<string, VertexElementUsage> usages;
static VertexChannelNames()
{
// Populate the lookup for TryDecodeUsage
usages = new Dictionary<string, VertexElementUsage>();
string[] names = Enum.GetNames(typeof(VertexElementUsage));
Array values = Enum.GetValues(typeof(VertexElementUsage));
for (int i = 0; i < names.Length; ++i)
usages.Add(names[i], (VertexElementUsage)values.GetValue(i));
}
/// <summary>
/// Gets the name of a binormal vector channel with the specified index.
/// This will typically contain Vector3 data.
/// </summary>
/// <param name="usageIndex">Zero-based index of the vector channel being retrieved.</param>
/// <returns>Name of the retrieved vector channel.</returns>
public static string Binormal(int usageIndex)
{
return EncodeName(VertexElementUsage.Binormal, usageIndex);
}
/// <summary>
/// Gets the name of a color channel with the specified index.
/// This will typically contain Vector3 data.
/// </summary>
/// <param name="usageIndex">Zero-based index of the color channel being retrieved.</param>
/// <returns>Name of the retrieved color channel.</returns>
public static string Color(int usageIndex)
{
return EncodeName(VertexElementUsage.Color, usageIndex);
}
/// <summary>
/// Gets a channel base name stub from the encoded string format.
/// </summary>
/// <param name="encodedName">Encoded string to be decoded.</param>
/// <returns>Extracted base name.</returns>
public static string DecodeBaseName(string encodedName)
{
if (string.IsNullOrEmpty(encodedName))
throw new ArgumentNullException("encodedName");
return encodedName.TrimEnd("0123456789".ToCharArray());
}
/// <summary>
/// Gets a channel usage index from the encoded format.
/// </summary>
/// <param name="encodedName">Encoded name to be decoded.</param>
/// <returns>Resulting channel usage index.</returns>
public static int DecodeUsageIndex(string encodedName)
{
if (string.IsNullOrEmpty(encodedName))
throw new ArgumentNullException("encodedName");
// Extract the base name
string baseName = DecodeBaseName(encodedName);
if (string.IsNullOrEmpty(baseName))
throw new InvalidOperationException("encodedName");
// Subtract the base name from the string and convert the remainder to an integer.
// TryParse solves the problem when name is just 'BlendIndicies' for example, in
// which case we default to index 0, assuming only 1 index.
int index = 0;
int.TryParse(encodedName.Substring(baseName.Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out index);
return index;
}
/// <summary>
/// Combines a channel name stub and usage index into a string name.
/// </summary>
/// <param name="baseName">A channel base name stub.</param>
/// <param name="usageIndex">A channel usage index.</param>
/// <returns>Resulting encoded name.</returns>
public static string EncodeName(string baseName, int usageIndex)
{
return baseName + usageIndex.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Combines a vertex declaration usage and usage index into a string name.
/// </summary>
/// <param name="vertexElementUsage">A vertex declaration.</param>
/// <param name="usageIndex">An index for the vertex declaration.</param>
/// <returns>Resulting encoded name.</returns>
public static string EncodeName(VertexElementUsage vertexElementUsage, int usageIndex)
{
return vertexElementUsage.ToString() + usageIndex.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets the name of the primary normal channel.
/// This will typically contain Vector3 data.
/// </summary>
/// <returns>Primary normal channel name.</returns>
public static string Normal()
{
return Normal(0);
}
/// <summary>
/// Gets the name of a normal channel with the specified index.
/// This will typically contain Vector3 data.
/// </summary>
/// <param name="usageIndex">Zero-based index of the normal channel being retrieved.</param>
/// <returns>Normal channel at the specified index.</returns>
public static string Normal(int usageIndex)
{
return EncodeName(VertexElementUsage.Normal, usageIndex);
}
/// <summary>
/// Gets the name of a tangent vector channel with the specified index.
/// This will typically contain Vector3 data.
/// </summary>
/// <param name="usageIndex">Zero-based index of the tangent vector channel being retrieved.</param>
/// <returns>Name of the retrieved tangent vector channel.</returns>
public static string Tangent(int usageIndex)
{
return EncodeName(VertexElementUsage.Tangent, usageIndex);
}
/// <summary>
/// Gets the name of a texture coordinate channel with the specified index.
/// This will typically contain Vector3 data.
/// </summary>
/// <param name="usageIndex">Zero-based index of the texture coordinate channel being retrieved.</param>
/// <returns>Name of the retrieved texture coordinate channel.</returns>
public static string TextureCoordinate(int usageIndex)
{
return EncodeName(VertexElementUsage.TextureCoordinate, usageIndex);
}
/// <summary>
/// Gets a vertex declaration usage enumeration from the encoded string format.
/// </summary>
/// <param name="encodedName">Encoded name of a vertex declaration.</param>
/// <param name="usage">Value of the declaration usage for the vertex declaration.</param>
/// <returns>true if the encoded name maps to a VertexElementUsage enumeration value; false otherwise.</returns>
public static bool TryDecodeUsage(string encodedName, out VertexElementUsage usage)
{
if (string.IsNullOrEmpty(encodedName))
throw new ArgumentNullException("encodedName");
// Extract the base name
string baseName = DecodeBaseName(encodedName);
if (string.IsNullOrEmpty(baseName))
throw new InvalidOperationException("encodedName");
return usages.TryGetValue(baseName, out usage);
}
/// <summary>
/// Gets the name of the primary animation weights channel.
/// This will typically contain data on the bone weights for a vertex channel. For more information, see BoneWeightCollection.
/// </summary>
/// <returns>Name of the primary animation weights channel.</returns>
public static string Weights()
{
return Weights(0);
}
/// <summary>
/// Gets the name of an animation weights channel at the specified index.
/// This will typically contain data on the bone weights for a vertex channel. For more information, see BoneWeightCollection.
/// </summary>
/// <param name="usageIndex">Index of the animation weight channel to be retrieved.</param>
/// <returns>Name of the retrieved animation weights channel.</returns>
public static string Weights(int usageIndex)
{
// This appears to be the odd one out that doesn't use the VertexElementUsage enum.
return EncodeName("Weights", usageIndex);
}
}
}
@@ -0,0 +1,230 @@
// 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 Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
/// <summary>
/// Provides methods and properties for maintaining the vertex data of a GeometryContent.
/// </summary>
/// <remarks>This class combines a collection of arbitrarily named data channels with a list of position indices that reference the Positions collection of the parent MeshContent.</remarks>
public sealed class VertexContent
{
VertexChannelCollection channels;
VertexChannel<int> positionIndices;
IndirectPositionCollection positions;
/// <summary>
/// Gets the list of named vertex data channels in the VertexContent.
/// </summary>
/// <value>Collection of vertex data channels.</value>
public VertexChannelCollection Channels { get { return channels; } }
/// <summary>
/// Gets the list of position indices.
/// </summary>
/// <value>Position of the position index being retrieved.</value>
/// <remarks>This list adds a level of indirection between the actual triangle indices and the Positions member of the parent. This indirection preserves the topological vertex identity in cases where a single vertex position is used by triangles that straddle a discontinuity in some other data channel.
/// For example, the following code gets the position of the first vertex of the first triangle in a GeometryContent object:
/// parent.Positions[Vertices.PositionIndices[Indices[0]]]</remarks>
public VertexChannel<int> PositionIndices { get { return positionIndices; } }
/// <summary>
/// Gets position data from the parent mesh object.
/// </summary>
/// <value>Collection of vertex positions for the mesh.</value>
/// <remarks>The collection returned from this call provides a virtualized view of the vertex positions for this batch. The collection uses the contents of the PositionIndices property to index into the parent Positions. This collection is read-only. If you need to modify any contained values, edit the PositionIndices or Positions members directly.</remarks>
public IndirectPositionCollection Positions { get { return positions; } }
/// <summary>
/// Number of vertices for the content.
/// </summary>
/// <value>Number of vertices.</value>
public int VertexCount { get { return positionIndices.Count; } }
/// <summary>
/// Constructs a VertexContent instance.
/// </summary>
internal VertexContent(GeometryContent geom)
{
positionIndices = new VertexChannel<int>("PositionIndices");
positions = new IndirectPositionCollection(geom, positionIndices);
channels = new VertexChannelCollection(this);
}
/// <summary>
/// Appends a new vertex index to the end of the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
/// </summary>
/// <param name="positionIndex">Index into the MeshContent.Positions member of the parent.</param>
/// <returns>Index of the new entry. This can be added to the Indices member of the parent.</returns>
public int Add(int positionIndex)
{
return positionIndices.Items.Add(positionIndex);
}
/// <summary>
/// Appends multiple vertex indices to the end of the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
/// </summary>
/// <param name="positionIndexCollection">Index into the Positions member of the parent.</param>
public void AddRange(IEnumerable<int> positionIndexCollection)
{
positionIndices.InsertRange(positionIndices.Items.Count, positionIndexCollection);
}
/// <summary>
/// Converts design-time vertex position and channel data into a vertex buffer format that a graphics device can recognize.
/// </summary>
/// <returns>A packed vertex buffer.</returns>
/// <exception cref="InvalidContentException">One or more of the vertex channel types are invalid or an unrecognized name was passed to VertexElementUsage.</exception>
public VertexBufferContent CreateVertexBuffer()
{
var vertexBuffer = new VertexBufferContent(positions.Count);
var stride = SetupVertexDeclaration(vertexBuffer);
// TODO: Verify enough elements in channels to match positions?
// Write out data in an interleaved fashion each channel at a time, for example:
// |------------------------------------------------------------|
// |POSITION[0] | NORMAL[0] |TEX0[0] | POSITION[1]| NORMAL[1] |
// |-----------------------------------------------|------------|
// #0 |111111111111|____________|________|111111111111|____________|
// #1 |111111111111|111111111111|________|111111111111|111111111111|
// #2 |111111111111|111111111111|11111111|111111111111|111111111111|
// #0: Write position vertices using stride to skip over the other channels:
vertexBuffer.Write(0, stride, positions);
var channelOffset = VertexBufferContent.SizeOf(typeof(Vector3));
foreach (var channel in Channels)
{
// #N: Fill in the channel within each vertex
var channelType = channel.ElementType;
vertexBuffer.Write(channelOffset, stride, channelType, channel);
channelOffset += VertexBufferContent.SizeOf(channelType);
}
return vertexBuffer;
}
private int SetupVertexDeclaration(VertexBufferContent result)
{
var offset = 0;
// We always have a position channel
result.VertexDeclaration.VertexElements.Add(new VertexElement(offset, VertexElementFormat.Vector3,
VertexElementUsage.Position, 0));
offset += VertexElementFormat.Vector3.GetSize();
// Optional channels
foreach (var channel in Channels)
{
VertexElementFormat format;
VertexElementUsage usage;
// Try to determine the vertex format
if (channel.ElementType == typeof(Single))
format = VertexElementFormat.Single;
else if (channel.ElementType == typeof(Vector2))
format = VertexElementFormat.Vector2;
else if (channel.ElementType == typeof(Vector3))
format = VertexElementFormat.Vector3;
else if (channel.ElementType == typeof(Vector4))
format = VertexElementFormat.Vector4;
else if (channel.ElementType == typeof(Color))
format = VertexElementFormat.Color;
else if (channel.ElementType == typeof(Byte4))
format = VertexElementFormat.Byte4;
else if (channel.ElementType == typeof(Short2))
format = VertexElementFormat.Short2;
else if (channel.ElementType == typeof(Short4))
format = VertexElementFormat.Short4;
else if (channel.ElementType == typeof(NormalizedShort2))
format = VertexElementFormat.NormalizedShort2;
else if (channel.ElementType == typeof(NormalizedShort4))
format = VertexElementFormat.NormalizedShort4;
else if (channel.ElementType == typeof(HalfVector2))
format = VertexElementFormat.HalfVector2;
else if (channel.ElementType == typeof(HalfVector4))
format = VertexElementFormat.HalfVector4;
else
throw new InvalidContentException(string.Format("Unrecognized vertex content type: '{0}'", channel.ElementType));
// Try to determine the vertex usage
if (!VertexChannelNames.TryDecodeUsage(channel.Name, out usage))
throw new InvalidContentException(string.Format("Unknown vertex element usage for channel '{0}'", channel.Name));
// Try getting the usage index
var usageIndex = VertexChannelNames.DecodeUsageIndex(channel.Name);
result.VertexDeclaration.VertexElements.Add(new VertexElement(offset, format, usage, usageIndex));
offset += format.GetSize();
}
result.VertexDeclaration.VertexStride = offset;
return offset;
}
/// <summary>
/// Inserts a new vertex index to the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
/// </summary>
/// <param name="index">Vertex index to be inserted.</param>
/// <param name="positionIndex">Position of new vertex index in the collection.</param>
public void Insert(int index, int positionIndex)
{
positionIndices.Items.Insert(index, positionIndex);
}
/// <summary>
/// Inserts multiple vertex indices to the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
/// </summary>
/// <param name="index">Vertex index to be inserted.</param>
/// <param name="positionIndexCollection">Position of the first element of the inserted range in the collection.</param>
public void InsertRange(int index, IEnumerable<int> positionIndexCollection)
{
positionIndices.InsertRange(index, positionIndexCollection);
}
/// <summary>
/// Removes a vertex index from the specified location in both PositionIndices and VertexChannel&lt;T&gt;.
/// </summary>
/// <param name="index">Index of the vertex to be removed.</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= VertexCount)
throw new ArgumentOutOfRangeException("index");
positionIndices.Items.RemoveAt(index);
foreach (var channel in channels)
channel.Items.RemoveAt(index);
}
/// <summary>
/// Removes a range of vertex indices from the specified location in both PositionIndices and VertexChannel&lt;T&gt;.
/// </summary>
/// <param name="index">Index of the first vertex index to be removed.</param>
/// <param name="count">Number of indices to remove.</param>
public void RemoveRange(int index, int count)
{
if (index < 0 || index >= VertexCount)
throw new ArgumentOutOfRangeException("index");
if (count < 0 || (index+count) > VertexCount)
throw new ArgumentOutOfRangeException("count");
positionIndices.RemoveRange(index, count);
foreach (var channel in channels)
channel.RemoveRange(index, count);
}
}
}