// 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
{
///
/// Provides methods and properties for maintaining the vertex data of a GeometryContent.
///
/// 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.
public sealed class VertexContent
{
VertexChannelCollection channels;
VertexChannel positionIndices;
IndirectPositionCollection positions;
///
/// Gets the list of named vertex data channels in the VertexContent.
///
/// Collection of vertex data channels.
public VertexChannelCollection Channels { get { return channels; } }
///
/// Gets the list of position indices.
///
/// Position of the position index being retrieved.
/// 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]]]
public VertexChannel PositionIndices { get { return positionIndices; } }
///
/// Gets position data from the parent mesh object.
///
/// Collection of vertex positions for the mesh.
/// 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.
public IndirectPositionCollection Positions { get { return positions; } }
///
/// Number of vertices for the content.
///
/// Number of vertices.
public int VertexCount { get { return positionIndices.Count; } }
///
/// Constructs a VertexContent instance.
///
internal VertexContent(GeometryContent geom)
{
positionIndices = new VertexChannel("PositionIndices");
positions = new IndirectPositionCollection(geom, positionIndices);
channels = new VertexChannelCollection(this);
}
///
/// 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.
///
/// Index into the MeshContent.Positions member of the parent.
/// Index of the new entry. This can be added to the Indices member of the parent.
public int Add(int positionIndex)
{
return positionIndices.Items.Add(positionIndex);
}
///
/// 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.
///
/// Index into the Positions member of the parent.
public void AddRange(IEnumerable positionIndexCollection)
{
positionIndices.InsertRange(positionIndices.Items.Count, positionIndexCollection);
}
///
/// Converts design-time vertex position and channel data into a vertex buffer format that a graphics device can recognize.
///
/// A packed vertex buffer.
/// One or more of the vertex channel types are invalid or an unrecognized name was passed to VertexElementUsage.
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;
}
///
/// Inserts a new vertex index to the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
///
/// Vertex index to be inserted.
/// Position of new vertex index in the collection.
public void Insert(int index, int positionIndex)
{
positionIndices.Items.Insert(index, positionIndex);
}
///
/// Inserts multiple vertex indices to the PositionIndices collection.
/// Other vertex channels will automatically be extended and the new indices populated with default values.
///
/// Vertex index to be inserted.
/// Position of the first element of the inserted range in the collection.
public void InsertRange(int index, IEnumerable positionIndexCollection)
{
positionIndices.InsertRange(index, positionIndexCollection);
}
///
/// Removes a vertex index from the specified location in both PositionIndices and VertexChannel<T>.
///
/// Index of the vertex to be removed.
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);
}
///
/// Removes a range of vertex indices from the specified location in both PositionIndices and VertexChannel<T>.
///
/// Index of the first vertex index to be removed.
/// Number of indices to remove.
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);
}
}
}