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 _vertexChannels; private readonly List _vertexChannelData; private bool _finishedCreation; private bool _finishedMesh; /// /// Gets or sets the current value for position merging of the mesh. /// public bool MergeDuplicatePositions { get; set; } /// /// Gets or sets the tolerance for . /// public float MergePositionTolerance { get; set; } /// /// Gets or sets the name of the current object being processed. /// public string Name { get { return _meshContent.Name; } set { _meshContent.Name = value; } } /// /// Reverses the triangle winding order of the specified mesh. /// public bool SwapWindingOrder { get; set; } private MeshBuilder(string name) { _meshContent = new MeshContent(); _vertexChannels = new List(); _vertexChannelData = new List(); _currentGeometryContent = new GeometryContent(); _currentOpaqueData = new OpaqueDataDictionary(); _geometryDirty = true; Name = name; } /// /// Adds a vertex into the index collection. /// /// Index of the inserted vertex, in the collection. /// This corresponds to the value returned by . 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(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(usage); _vertexChannels.Add(channel); _vertexChannelData.Add(default(T)); _currentGeometryContent.Vertices.Channels.Add(usage, null); return _vertexChannels.Count - 1; } /// /// Inserts the specified vertex position into the vertex channel. /// /// Value of the x component of the vector. /// Value of the y component of the vector. /// Value of the z component of the vector. /// Index of the inserted vertex. public int CreatePosition(float x, float y, float z) { return CreatePosition(new Vector3(x, y, z)); } /// /// Inserts the specified vertex position into the vertex channel at the specified index. /// /// Value of the vertex being inserted. /// Index of the vertex being inserted. 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; } /// /// Ends the creation of a mesh. /// /// Resultant mesh. 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; } /// /// Sets the material for the next triangles. /// /// Material for the next triangles. /// /// Sets the material for the triangles being defined next. This material /// and the opaque data dictionary, set with /// define the 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 /// object. /// public void SetMaterial(MaterialContent material) { if (_currentMaterial == material) return; _currentMaterial = material; _geometryDirty = true; } /// /// Sets the opaque data for the next triangles. /// /// Opaque data dictionary for the next triangles. /// /// Sets the opaque data dictionary for the triangles being defined next. This dictionary /// and the material, set with , define the /// 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 object. /// public void SetOpaqueData(OpaqueDataDictionary opaqueData) { if (_currentOpaqueData == opaqueData) return; _currentOpaqueData = opaqueData; _geometryDirty = true; } /// /// Sets the specified vertex data with new data. /// /// Index of the vertex data channel being set. This should match the index returned by CreateVertexChannel. /// New data values for the vertex data. The data type being set must match the data type for the vertex channel specified by vertexDataIndex. 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; } /// /// Initializes the creation of a mesh. /// /// Name of the mesh. /// Object used when building the mesh. public static MeshBuilder StartMesh(string name) { return new MeshBuilder(name); } } }