/// <summary> /// Converts input channels that are used for colors from Vector3/Vector4 /// to single float format, if necessary. /// </summary> /// <param name="inputChannels">Vertex Channels as read from COLLADA file</param> private void ConvertColorChannels(List <VertexChannel> inputChannels) { foreach (VertexChannel channel in inputChannels) { var usage = channel.Description.VertexElementUsage; var format = channel.Description.VertexElementFormat; if (usage != VertexElementUsage.Color) { continue; // only relevant for colors } if (format == VertexElementFormat.Single) { continue; // nothing to do } // Create updated vertex element description where each element is a single VertexElement newDesc = new VertexElement() { Offset = 0, UsageIndex = channel.Description.UsageIndex, VertexElementFormat = VertexElementFormat.Single, VertexElementUsage = VertexElementUsage.Color }; // Old stride is 3 or 4 (corresponding to Vector3 or Vector4) int oldStride = channel.Source.Stride; float[] oldData = channel.Source.Data; // Create new source where each color is only represented by one single VertexSource newSource = new VertexSource(); newSource.Stride = 1; // one float per color newSource.Data = new float[oldData.Length / oldStride]; for (int i = 0; i < newSource.Data.Length; i++) { // project start index to old data set (with $oldStride components per color) int j = i * oldStride; // Construct color from three or four floats in range [0,1] Color color = (oldStride == 3) ? new Color(oldData[j + 0], oldData[j + 1], oldData[j + 2]) : new Color(oldData[j + 0], oldData[j + 1], oldData[j + 2], oldData[j + 3]); newSource.Data[i] = ConvertColorToSingle(color); } // Update description and source of channel channel.Description = newDesc; channel.Source = newSource; } }
/// <summary> /// Creates a new Vertex Channel /// </summary> /// <param name="source">Used vertex source</param> /// <param name="description">Vertex element description</param> /// <param name="indices">Indices</param> public VertexChannel(VertexSource source, VertexElement description) { Source = source; Description = description; }
/// <summary> /// Creates a vertex container from a set of "raw" vertex channels as read from the COLLADA file. /// Hence, it is assumed that each channel uses its own source (rather than every channel using /// the same single source). /// </summary> /// <param name="inputChannels">Original Input Channels from COLLADA file</param> public VertexContainer(List <VertexChannel> inputChannels) { // Check for basic requirements if (inputChannels.Any(c => c.Description.VertexElementUsage == VertexElementUsage.Position) == false) { throw new ArgumentException("Geometry has not all needed information. At least Positions are necessary!"); } // Convert Colors to single values, if necessary ConvertColorChannels(inputChannels); // Number of floats per vertex _vertexSize = CalculateVertexSize(inputChannels); // Expected number of indices int numIndices = inputChannels.First().Indices.Length; // vertex buffer with an expected number of 3/4 of the number of indices List <float> vbuffer = new List <float>(numIndices * 3 / 4); // Remember the position of distinct vertices to avoid duplicates Dictionary <VertexKey, int> usedVertices = new Dictionary <VertexKey, int>(numIndices * 3 / 4); // Indices referencing the new vertex buffer (vbuffer) List <int> indexList = new List <int>(numIndices); // Go through all indices to create vertices for (int i = 0; i < numIndices; i++) { VertexKey key = new VertexKey(inputChannels, i); int usedIndex = 0; if (usedVertices.TryGetValue(key, out usedIndex)) { // This vertex was already used, its index is "usedIndex" indexList.Add(usedIndex); } else { // If the vertex is unknown, add it to the vertex container (channel-wise) // and remember that is has been used and the corresponding index int index = vbuffer.Count / _vertexSize; // Add all elements of the current vertex to the vertex buffer foreach (VertexChannel channel in inputChannels) { float[] elementData = new float[channel.Source.Stride]; channel.GetValue(i, ref elementData, 0); // origin of texture coordinates in XNA is top left, while // in COLLADA it is bottom left. Therefore they need to be // converted here if (channel.Description.VertexElementUsage == VertexElementUsage.TextureCoordinate) { elementData[1] = 1 - elementData[1]; } vbuffer.AddRange(elementData); } // Remember that this vertex combination was used before // and store the index where it can be found in the // vertex container usedVertices.Add(key, index); // Add reference to the just created vertex to the index list / buffer indexList.Add(index); } } // Create adequate vertex channels int offset = 0; foreach (VertexChannel inputChannel in inputChannels) { VertexSource newSource = new VertexSource() { Offset = offset // the element-offset within the vertex buffer }; VertexElement desc = new VertexElement(offset, inputChannel.Description.VertexElementFormat, inputChannel.Description.VertexElementUsage, inputChannel.Description.UsageIndex); VertexChannel newChannel = new VertexChannel(newSource, desc); _vertexChannels.Add(newChannel); offset += inputChannel.Source.Stride; } // Swap winding order for (int i = 0; i < indexList.Count; i += 3) { int swap = indexList[i + 1]; indexList[i + 1] = indexList[i + 2]; indexList[i + 2] = swap; } _data = vbuffer.ToArray(); _indices = indexList.ToArray(); // Update Source Data reference off all vertex channels foreach (VertexChannel channel in _vertexChannels) { // Every channel uses the same source now (global vertex buffer) channel.Source.Data = _data; // Every channel also uses the same indices channel.Indices = _indices; // The stride of one entry containing all elements for one vertex channel.Source.Stride = _vertexSize; } }