static void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex) { // create a map of Name->Index of the bones BoneContent skeleton = MeshHelper.FindSkeleton(geometry.Parent); Dictionary <string, int> boneIndices = new Dictionary <string, int>(); IList <BoneContent> flattenedBones = MeshHelper.FlattenSkeleton(skeleton); for (int i = 0; i < flattenedBones.Count; i++) { boneIndices.Add(flattenedBones[i].Name, i); } // convert all of our bone weights into the correct indices and weight values VertexChannel <BoneWeightCollection> inputWeights = geometry.Vertices.Channels[vertexChannelIndex] as VertexChannel <BoneWeightCollection>; Vector4[] outputIndices = new Vector4[inputWeights.Count]; Vector4[] outputWeights = new Vector4[inputWeights.Count]; for (int i = 0; i < inputWeights.Count; i++) { ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i, geometry); } // create our new channel names int usageIndex = VertexChannelNames.DecodeUsageIndex(inputWeights.Name); string indicesName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex); string weightsName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex); // add in the index and weight channels geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices); geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights); // remove the original weights channel geometry.Vertices.Channels.RemoveAt(vertexChannelIndex); }
private int[] ComputeBoneSet(GeometryContent geometry, ContentProcessorContext context, string asset, Dictionary <string, int> boneIndices) { SortedDictionary <int, bool> indicesInUse = new SortedDictionary <int, bool>(); foreach (VertexChannel vc in geometry.Vertices.Channels) { string str = VertexChannelNames.DecodeBaseName(vc.Name); if (str == "Weights") { VertexChannel <BoneWeightCollection> channel = vc as VertexChannel <BoneWeightCollection>; if (vc == null) { continue; } for (int n = 0; n < channel.Count; n++) { VertexWeightsInUse(context, asset, channel[n], boneIndices, indicesInUse); } } } int[] values = new int[indicesInUse.Count]; int i = 0; foreach (int index in indicesInUse.Keys) { values[i++] = index; } return(values); }
private static void ProcessWeightsChannel(ContentProcessorContext context, string asset, GeometryContent geometry, int vertexChannelIndex, Dictionary <string, int> boneIndices, Dictionary <int, int> boneRemap) { if (boneIndices == null) { throw new InvalidContentException("Mesh has bone weights with no skeleton"); } VertexChannelCollection channels = geometry.Vertices.Channels; VertexChannel channel2 = channels[vertexChannelIndex]; VertexChannel <BoneWeightCollection> channel = channel2 as VertexChannel <BoneWeightCollection>; Byte4[] outputIndices = new Byte4[channel.Count]; Vector4[] outputWeights = new Vector4[channel.Count]; for (int i = 0; i < channel.Count; i++) { BoneWeightCollection inputWeights = channel[i]; ConvertVertexWeights(context, asset, inputWeights, boneIndices, outputIndices, outputWeights, i, geometry, boneRemap); } int usageIndex = VertexChannelNames.DecodeUsageIndex(channel.Name); string name = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex); string str = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex); channels.Insert <Byte4>(vertexChannelIndex + 1, name, outputIndices); channels.Insert <Vector4>(vertexChannelIndex + 2, str, outputWeights); channels.RemoveAt(vertexChannelIndex); }
static void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex, SerializableSkeleton skeleton) { Dictionary <string, int> boneIndices = skeleton.boneIndexByName; // convert all of our bone weights into the correct indices and weight values VertexChannel <BoneWeightCollection> inputWeights = geometry.Vertices.Channels[vertexChannelIndex] as VertexChannel <BoneWeightCollection>; Vector4[] outputIndices = new Vector4[inputWeights.Count]; Vector4[] outputWeights = new Vector4[inputWeights.Count]; for (int i = 0; i < inputWeights.Count; i++) { ConvertWeights(inputWeights[i], boneIndices, outputIndices, outputWeights, i, geometry); } // create our new channel names int usageIndex = VertexChannelNames.DecodeUsageIndex(inputWeights.Name); string indicesName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendIndices, usageIndex); string weightsName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendWeight, usageIndex); // add in the index and weight channels geometry.Vertices.Channels.Insert(vertexChannelIndex + 1, indicesName, outputIndices); geometry.Vertices.Channels.Insert(vertexChannelIndex + 2, weightsName, outputWeights); // remove the original weights channel geometry.Vertices.Channels.RemoveAt(vertexChannelIndex); }
private static void ColorMaterials(MeshContent mesh) { foreach (GeometryContent meshPart in mesh.Geometry) { Vector4 constColor = MatColor(meshPart); bool foundColor = false; foreach (VertexChannel channel in meshPart.Vertices.Channels) { if (channel.Name == VertexChannelNames.Color(0)) { foundColor = true; VertexChannel <Vector4> colorChannel = (VertexChannel <Vector4>)channel; for (int i = 0; i < colorChannel.Count; ++i) { colorChannel[i] = constColor; } } } if (!foundColor) { int cnt = meshPart.Vertices.VertexCount; Vector4[] colors = new Vector4[cnt]; for (int i = 0; i < cnt; ++i) { colors[i] = constColor; } meshPart.Vertices.Channels.Add <Vector4>( VertexChannelNames.Color(0).ToString(), colors); } } }
public static bool NeedsSplitting(MeshContent mesh, int maxBones) { SortedDictionary <string, object> skinnedBones = new SortedDictionary <string, object>(); foreach (GeometryContent geom in mesh.Geometry) { VertexChannel <BoneWeightCollection> weightChannel = null; foreach (VertexChannel channel in geom.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { weightChannel = (VertexChannel <BoneWeightCollection>)channel; break; } } if (weightChannel != null) { foreach (BoneWeightCollection weights in weightChannel) { foreach (BoneWeight weight in weights) { if (!skinnedBones.ContainsKey(weight.BoneName)) { skinnedBones.Add(weight.BoneName, null); } } } } } return(skinnedBones.Keys.Count > maxBones); }
/// <summary> /// This function removes geometry that contains no bone weights, because the ModelProcessor /// will throw an exception if we give it geometry content like that. /// </summary> /// <param name="node"></param> /// <param name="context"></param> static void RemoveInvalidGeometry(NodeContent node, ContentProcessorContext context) { MeshContent meshContent = node as MeshContent; if (meshContent != null) { // Maintain a list of all the geometry that was invalid that we will be removing List <GeometryContent> removeGeometry = new List <GeometryContent>(); foreach (GeometryContent geometry in meshContent.Geometry) { VertexChannelCollection channels = geometry.Vertices.Channels; // Does this geometry contain bone weight information? if (geometry.Vertices.Channels.Contains(VertexChannelNames.Weights(0))) { bool removed = false; VertexChannel <BoneWeightCollection> weights = geometry.Vertices.Channels.Get <BoneWeightCollection>(VertexChannelNames.Weights(0)); foreach (BoneWeightCollection collection in weights) { // If we don't have any weights, then this isn't going to be good. The geometry has no bone weights, // so lets just remove it. if (collection.Count <= 0) { removeGeometry.Add(geometry); removed = true; break; } else { // Otherwise, normalize the weights. This call is probably unnecessary. collection.NormalizeWeights(4); } } //If we removed something from this geometry, just remove the whole geometry - there's no point in going farther if (removed) { break; } } } // Remove all the invalid geometry we found, and log a warning. foreach (GeometryContent geometry in removeGeometry) { meshContent.Geometry.Remove(geometry); context.Logger.LogWarning(null, null, "Mesh part {0} has been removed because it has no bone weights associated with it.", geometry.Name); } } // Recursively call this function for each child foreach (NodeContent child in node.Children) { RemoveInvalidGeometry(child, context); } }
private void ProcessGeometry(GeometryContent xnaGeometry) { // find and process the geometry's bone weights for (int i = 0; i < xnaGeometry.Vertices.Channels.Count; i++) { string channelName = xnaGeometry.Vertices.Channels[i].Name; string baseName = VertexChannelNames.DecodeBaseName(channelName); if (baseName == "Weights") { ProcessWeightsChannel(xnaGeometry, i, outputModel.skeleton); } } // retrieve the four vertex channels we require for CPU skinning. we ignore any // other channels the model might have. string normalName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.Normal, 0); string texCoordName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.TextureCoordinate, 0); string blendWeightName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendWeight, 0); string blendIndexName = VertexChannelNames.EncodeName(Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendIndices, 0); VertexChannel <Vector3> normals = xnaGeometry.Vertices.Channels[normalName] as VertexChannel <Vector3>; VertexChannel <Vector2> texCoords = xnaGeometry.Vertices.Channels[texCoordName] as VertexChannel <Vector2>; VertexChannel <Vector4> blendWeights = xnaGeometry.Vertices.Channels[blendWeightName] as VertexChannel <Vector4>; VertexChannel <Vector4> blendIndices = xnaGeometry.Vertices.Channels[blendIndexName] as VertexChannel <Vector4>; // create our array of vertices int triangleCount = xnaGeometry.Indices.Count / 3; SerializableVertex[] vertices = new SerializableVertex[xnaGeometry.Vertices.VertexCount]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = new SerializableVertex { position = xnaGeometry.Vertices.Positions[i], normal = normals[i], texture = texCoords[i], blendweights = blendWeights[i], blendindices = blendIndices[i] }; } int[] indices = new int[xnaGeometry.Indices.Count]; for (int i = 0; i < xnaGeometry.Indices.Count; i++) { indices[i] = xnaGeometry.Indices[i]; } SerializableMesh mesh = new SerializableMesh(); mesh.name = string.Format("mesh_{0}_{1}", outputModel.meshList.Count, xnaGeometry.Name); mesh.textureName = GetTextureName(xnaGeometry); mesh.vertices = vertices; mesh.indices = indices; outputModel.meshList.Add(mesh); }
Tuple <VertexChannel, VertexChannel> GenerateJointChannels(Vector4[] jointIndices, Vector3[] jointWeights, int[] positionIndices) { if (jointIndices.Length == 0) { return(null); } VertexSource indexSource = new VertexSource(); VertexSource weightSource = new VertexSource(); indexSource.Stride = 4; indexSource.Data = new float[jointIndices.Length * 4]; weightSource.Stride = 3; weightSource.Data = new float[jointWeights.Length * 3]; for (int i = 0; i < jointIndices.Length; i++) { indexSource.Data[i * 4 + 0] = jointIndices[i].X; indexSource.Data[i * 4 + 1] = jointIndices[i].Y; indexSource.Data[i * 4 + 2] = jointIndices[i].Z; indexSource.Data[i * 4 + 3] = jointIndices[i].W; } for (int i = 0; i < jointWeights.Length; i++) { weightSource.Data[i * 3 + 0] = jointWeights[i].X; weightSource.Data[i * 3 + 1] = jointWeights[i].Y; weightSource.Data[i * 3 + 2] = jointWeights[i].Z; } VertexElement indexDesc = new VertexElement(); indexDesc.Offset = 0; indexDesc.UsageIndex = 0; indexDesc.VertexElementFormat = VertexElementFormat.Vector4; indexDesc.VertexElementUsage = VertexElementUsage.BlendIndices; VertexElement weightDesc = new VertexElement(); weightDesc.Offset = 0; weightDesc.UsageIndex = 0; weightDesc.VertexElementFormat = VertexElementFormat.Vector3; weightDesc.VertexElementUsage = VertexElementUsage.BlendWeight; VertexChannel indexChannel = new VertexChannel(indexSource, indexDesc); VertexChannel weightChannel = new VertexChannel(weightSource, weightDesc); indexChannel.Indices = new int[positionIndices.Length]; weightChannel.Indices = new int[positionIndices.Length]; Array.Copy(positionIndices, indexChannel.Indices, positionIndices.Length); Array.Copy(positionIndices, weightChannel.Indices, positionIndices.Length); return(new Tuple <VertexChannel, VertexChannel>(indexChannel, weightChannel)); }
private void GetWeightChannel() { foreach (VertexChannel channel in geom.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { weightChannel = (VertexChannel <BoneWeightCollection>)channel; break; } } }
void ProcessGeometry(GeometryContent geometry) { // find and process the geometry's bone weights for (int i = 0; i < geometry.Vertices.Channels.Count; i++) { string channelName = geometry.Vertices.Channels[i].Name; string baseName = VertexChannelNames.DecodeBaseName(channelName); if (baseName == "Weights") { ProcessWeightsChannel(geometry, i); } } // retrieve the four vertex channels we require for CPU skinning. we ignore any // other channels the model might have. string normalName = VertexChannelNames.EncodeName(VertexElementUsage.Normal, 0); string texCoordName = VertexChannelNames.EncodeName(VertexElementUsage.TextureCoordinate, 0); string blendWeightName = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0); string blendIndexName = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, 0); VertexChannel <Vector3> normals = geometry.Vertices.Channels[normalName] as VertexChannel <Vector3>; VertexChannel <Vector2> texCoords = geometry.Vertices.Channels[texCoordName] as VertexChannel <Vector2>; VertexChannel <Vector4> blendWeights = geometry.Vertices.Channels[blendWeightName] as VertexChannel <Vector4>; VertexChannel <Vector4> blendIndices = geometry.Vertices.Channels[blendIndexName] as VertexChannel <Vector4>; // create our array of vertices int triangleCount = geometry.Indices.Count / 3; CpuVertex[] vertices = new CpuVertex[geometry.Vertices.VertexCount]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = new CpuVertex { Position = geometry.Vertices.Positions[i], Normal = normals[i], TextureCoordinate = texCoords[i], BlendWeights = blendWeights[i], BlendIndices = blendIndices[i] }; } // Convert the input material. MaterialContent material = ProcessMaterial(geometry.Material); // Add the new piece of geometry to our output model. outputModel.AddModelPart(triangleCount, geometry.Indices, vertices, material as BasicMaterialContent); }
/// <summary> /// Processes a single node in the node hierarchy. Recursively processes children. /// </summary> /// <param name="node">The node to process.</param> private void ProcessNode(NodeContent node, StaticModelContent staticModel, ContentProcessorContext context, ref int indexOffset, ref int vertexOffset) { MeshContent mesh = node as MeshContent; if (mesh != null) { GenerateNTBData(mesh); foreach (GeometryContent geometry in mesh.Geometry) { if (!geometry.Vertices.Channels.Contains("Normal0") || !geometry.Vertices.Channels.Contains("TextureCoordinate0") || !geometry.Vertices.Channels.Contains("Binormal0") || !geometry.Vertices.Channels.Contains("Tangent0")) { // Only complain about normals/texture coordinates, since binormal and tangent data should have been computed for us and would only have failed if normal/texture coordinate data did not exist. throw new Exception("Geometry must contain Normal/Texture Coordinate channels."); } VertexChannel <Vector3> normals = geometry.Vertices.Channels.Get <Vector3>("Normal0"); VertexChannel <Vector3> binormals = geometry.Vertices.Channels.Get <Vector3>("Binormal0"); VertexChannel <Vector3> tangents = geometry.Vertices.Channels.Get <Vector3>("Tangent0"); VertexChannel <Vector2> texCoord0 = geometry.Vertices.Channels.Get <Vector2>("TextureCoordinate0"); staticModel.VertexContent.Write <Vector3>(sizeof(float) * 0 + vertexOffset, sizeof(float) * 14, geometry.Vertices.Positions); staticModel.VertexContent.Write <Vector2>(sizeof(float) * 3 + vertexOffset, sizeof(float) * 14, texCoord0); staticModel.VertexContent.Write <Vector3>(sizeof(float) * 5 + vertexOffset, sizeof(float) * 14, normals); staticModel.VertexContent.Write <Vector3>(sizeof(float) * 8 + vertexOffset, sizeof(float) * 14, binormals); staticModel.VertexContent.Write <Vector3>(sizeof(float) * 11 + vertexOffset, sizeof(float) * 14, tangents); vertexOffset += geometry.Vertices.Positions.Count * sizeof(float) * 14; int[] indices = new int[geometry.Indices.Count]; geometry.Indices.CopyTo(indices, 0); for (int i = 0; i < indices.Length; ++i) { indices[i] += indexOffset; } indexOffset += geometry.Vertices.Positions.Count; staticModel.IndexContent.AddRange(indices); } } foreach (NodeContent child in node.Children) { ProcessNode(child, staticModel, context, ref indexOffset, ref vertexOffset); } }
public VertexData() { Positions = new VertexChannel<Vector3>(); Normals = new VertexChannelCollection<Vector3>(); Tangents = new VertexChannelCollection<Vector3>(); Binormals = new VertexChannelCollection<Vector3>(); Colors = new VertexChannelCollection<Color4>(); TextureCoordinates = new VertexChannelCollection<Vector2>(); Positions.Changed += ChannelChanged; Normals.Changed += ChannelChanged; Tangents.Changed += ChannelChanged; Binormals.Changed += ChannelChanged; Colors.Changed += ChannelChanged; TextureCoordinates.Changed += ChannelChanged; }
private void CreatePaletteIndices(MeshContent mesh) { foreach (GeometryContent meshPart in mesh.Geometry) { int meshIndex = (int)mesh.OpaqueData["MeshIndex"]; BoneIndexer indexer = indexers[meshIndex]; foreach (VertexChannel channel in meshPart.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { VertexChannel <BoneWeightCollection> vc = (VertexChannel <BoneWeightCollection>)channel; foreach (BoneWeightCollection boneWeights in vc) { foreach (BoneWeight weight in boneWeights) { indexer.GetBoneIndex(weight.BoneName); } } } } } }
/// <summary> /// This is worth some comments. /// The PositionIndices are a mapping from the indices in a GeometryContent /// to the Positions in its parent MeshContent. /// Note that the GeometryContent also has Positions, but these are extracted /// from the MeshContent automatically using the PositionIndices. /// All other Channels are directly indexed by the Indices. /// So: /// pos = MeshContent.Positions[GeoCont.Verts.PosIndices[GeoCont.Indices[i]]] /// but /// pos = GeoCont.Verts.Positions[GeoCont.Indices[i]] /// /// When we add some position indices to the GeoCont.Vertices, the positions /// are automatically set, because we are giving PositionIndices which map /// into the parent MeshContent. This assumes the positions in question are /// already in the MeshContent.Positions (which is the case here, because src /// and dst are both children of the same MeshContent). /// The additional channels are initialized to some unspecified default values, /// but they are there now, so all we need to do is copy them from src into dst. /// Since they are directly indexed by Indices, we know that their indices /// are simply offset by the number of verts that were in dst before we added /// in src. Similarly, we add in the src Indices offset by dst's original /// number of verts. /// </summary> /// <param name="dst"></param> /// <param name="src"></param> private static void AddGeometry(GeometryContent dst, GeometryContent src) { int baseIndex = dst.Vertices.VertexCount; int[] posindices = new int[src.Vertices.VertexCount]; for (int i = 0; i < src.Vertices.VertexCount; ++i) { posindices[i] = src.Vertices.PositionIndices[i]; } dst.Vertices.AddRange(posindices); /// Append src channel data to dst for (int iChan = 0; iChan < src.Vertices.Channels.Count; ++iChan) { VertexChannel srcChannel = src.Vertices.Channels[iChan]; VertexChannel dstChannel = dst.Vertices.Channels[iChan]; for (int i = 0; i < src.Vertices.VertexCount; ++i) { dstChannel[i + baseIndex] = srcChannel[i]; } } for (int i = 0; i < src.Indices.Count; ++i) { dst.Indices.Add(src.Indices[i] + baseIndex); } /// Copy over any opaqueData foreach (KeyValuePair <string, object> k in src.OpaqueData) { if (!dst.OpaqueData.ContainsKey(k.Key)) { dst.OpaqueData.Add(k.Key, k.Value); } } }
protected virtual void ProcessGeometry(GeometryContent geometry, ContentProcessorContext context) { if (bakeMeshTransform) { Matrix vertexTransform = geometry.Parent.AbsoluteTransform; Matrix vectorTransform = Matrix.Transpose(Matrix.Invert(vertexTransform)); foreach (VertexChannel vertexChannel in geometry.Vertices.Channels) { if (IsVectorChannel(vertexChannel) && vertexChannel.ElementType == typeof(Vector3)) { // Cast for a channel of type Vector3 VertexChannel <Vector3> vectorChannel = (VertexChannel <Vector3>)vertexChannel; // Transform all the vectors for (int i = 0; i < vectorChannel.Count; i++) { vectorChannel[i] = Vector3.Transform(vectorChannel[i], vectorTransform); vectorChannel[i].Normalize(); } } } } }
/// <summary> /// Constructs a VertexContent instance. /// </summary> internal VertexContent() { channels = new VertexChannelCollection(this); positionIndices = new VertexChannel<int>("PositionIndices"); positions = new IndirectPositionCollection(); }
/// <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> /// 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; } }
/// <summary> /// Imports a piece of geometry, but does only support meshes, no NURBS. /// </summary> /// <param name="xmlGeometryNode">XML node of the geometry</param> /// <exception cref="Exception">Only works with meshes</exception> Mesh ImportGeometry(XmlNode xmlGeometryNode, ColladaModel model) { // Find the mesh node XmlNode xmlMeshNode = xmlGeometryNode.SelectSingleNode(".//mesh"); if (xmlMeshNode == null) { throw new Exception("No supported geometry (Mesh) found"); } // Determine number of mesh parts XmlNodeList xmlTriangles = xmlMeshNode.SelectNodes("triangles|polygons|polylist"); if (xmlTriangles.Count == 0) { throw new Exception("No triangles found in mesh. Only triangles are supported"); } if (xmlTriangles[0].Name != "triangles") { // If there are polygons or a polylist, check that all of them are triangles if (xmlTriangles[0].Attributes["vcount"] != null) { var vcounts = XmlUtil.ParseInts(xmlTriangles[0].Attributes["vcount"].Value); var nonTriangles = vcounts.Where(count => count != 3); if (nonTriangles.Any()) { throw new Exception("Found polygon with " + nonTriangles.First() + " elements. Only triangles are supported"); } } } // Source data for this mesh used by all mesh parts. List <VertexSource> sources = ReadSources(xmlMeshNode); Vector4[] jointIndices; Vector3[] jointWeights; // Skinning Information, if available GetJointWeightsAndIndices(xmlMeshNode, model, out jointIndices, out jointWeights); if (sources.Count == 0) { throw new Exception("No data found"); } //------------------------------------------------------- // Create Mesh //------------------------------------------------------- Mesh mesh = new Mesh(); mesh.Name = XmlUtil.GetName(xmlGeometryNode); mesh.MeshParts = new MeshPart[xmlTriangles.Count]; // A mesh container for every mesh part, since every mesh part may use different // vertex types. This can be optimized in the content processor, if needed mesh.VertexContainers = new VertexContainer[xmlTriangles.Count]; string[] semantics = new string[] { "VERTEX", "COLOR", "NORMAL", "TEXCOORD", "TEXTANGENT", "TEXBINORMAL" }; //------------------------------------------------------- // Create Mesh Parts //------------------------------------------------------- for (int i = 0; i < xmlTriangles.Count; i++) { XmlNode xmlPart = xmlTriangles[i]; int numTriangles = int.Parse(xmlPart.Attributes["count"].Value); List <int> indexStream = new List <int>(numTriangles * 3); var pNodes = xmlPart.SelectNodes("p"); if (pNodes.Count > 1) { // Indices are scattered among numTriangles <p> tags foreach (XmlNode p in pNodes) { indexStream.AddRange(XmlUtil.ParseInts(p.InnerText)); } } else { // Indices are contained in one <p> tag indexStream.AddRange(XmlUtil.ParseInts(pNodes[0].InnerText)); } int[] indices = indexStream.ToArray(); MeshPart part = new MeshPart(); try { if (xmlPart.Attributes["material"] == null) { throw new Exception("no material attribute found"); } part.MaterialName = FindMaterial(xmlGeometryNode, xmlPart.Attributes["material"].Value); } catch (Exception) { // No Material found part.MaterialName = null; } // Read Vertex Channels List <VertexChannel> vertexChannels = new List <VertexChannel>(); foreach (String semantic in semantics) { XmlNode input = xmlPart.SelectSingleNode(".//input[@semantic='" + semantic + "']"); if (input == null) { continue; // no such vertex channel defined } int offset; String sourceId; if (!input.TryGetAttribute("source", out sourceId)) { throw new Exception("Referenced source of input with '" + semantic + "' semantic not found"); } if (!input.TryGetAttribute("offset", out offset)) { throw new Exception("No offset attribute of input with '" + semantic + "' semantic found"); } sourceId = sourceId.Replace("#", ""); VertexSource source = sources.Where(s => s.GlobalID.Equals(sourceId)).FirstOrDefault(); if (source == null) { if (semantic.Equals("VERTEX")) { sourceId = xmlGeometryNode.SelectSingleNode(".//input[@semantic='POSITION']/@source").InnerText.Substring(1); source = sources.Where(s => s.GlobalID.Equals(sourceId)).FirstOrDefault(); } if (source == null) { throw new Exception("Source '" + sourceId + "' not found"); } } VertexElement desc = new VertexElement(); desc.Offset = offset; desc.UsageIndex = 0; desc.VertexElementFormat = GetVertexElementFormat(source, semantic); desc.VertexElementUsage = GetVertexElementUsage(semantic); VertexChannel channel = new VertexChannel(source, desc); channel.CopyIndices(indices, offset, numTriangles * 3); vertexChannels.Add(channel); } var jointChannels = GenerateJointChannels(jointIndices, jointWeights, vertexChannels.Where(c => c.Description.VertexElementUsage == VertexElementUsage.Position).First().Indices); if (jointChannels != null) { vertexChannels.Add(jointChannels.Item1); vertexChannels.Add(jointChannels.Item2); } part.Vertices = new VertexContainer(vertexChannels); part.Indices = part.Vertices.Indices; mesh.VertexContainers[i] = part.Vertices; mesh.MeshParts[i] = part; } return(mesh); }
/// <summary> /// Breaks the input mesh into separate un-indexed triangles. /// </summary> /// <param name="input">Input MeshContent node.</param> /// <returns>Broken MeshContent</returns> private MeshContent ProcessMesh(NodeContent input) { MeshBuilder builder = MeshBuilder.StartMesh("model"); MeshContent mesh = input as MeshContent; List <Vector3> normalList = new List <Vector3>(); List <Vector2> texCoordList = new List <Vector2>(); if (mesh != null) { int normalChannel = builder.CreateVertexChannel <Vector3>( VertexChannelNames.Normal()); int texChannel = builder.CreateVertexChannel <Vector2>( VertexChannelNames.TextureCoordinate(0)); foreach (GeometryContent geometry in mesh.Geometry) { IndirectPositionCollection positions = geometry.Vertices.Positions; VertexChannel <Vector3> normals = geometry.Vertices.Channels.Get <Vector3>( VertexChannelNames.Normal()); VertexChannel <Vector2> texCoords = geometry.Vertices.Channels.Get <Vector2>( VertexChannelNames.TextureCoordinate(0)); // Copy the positions over // To do that, we traverse the indices and grab the indexed // position and add it to the new mesh. This in effect will // duplicate positions in the mesh reversing the compacting // effect of using index buffers. foreach (int i in geometry.Indices) { builder.CreatePosition(positions[i]); // Save the normals and the texture coordinates for additon to // the mesh later. normalList.Add(normals[i]); texCoordList.Add(texCoords[i]); } } int index = 0; foreach (GeometryContent geometry in mesh.Geometry) { // Save the material to the new mesh. builder.SetMaterial(geometry.Material); // Now we create the Triangles. // To do that, we simply generate an index list that is sequential // from 0 to geometry.Indices.Count // This will create an index buffer that looks like: 0,1,2,3,4,5,... for (int i = 0; i < geometry.Indices.Count; i++) { // Set the normal for the current vertex builder.SetVertexChannelData(normalChannel, normalList[index]); // Set the texture coordinates for the current vertex builder.SetVertexChannelData(texChannel, texCoordList[index]); builder.AddTriangleVertex(index); index++; } } } MeshContent finalMesh = builder.FinishMesh(); // Copy the transform over from the source mesh to retain parent/child // relative transforms. finalMesh.Transform = input.Transform; // Now we take the new MeshContent and calculate the centers of all the // triangles. The centers are needed so that we can rotate the triangles // around them as we shatter the model. foreach (GeometryContent geometry in finalMesh.Geometry) { Vector3[] triangleCenters = new Vector3[geometry.Indices.Count / 3]; Vector3[] trianglePoints = new Vector3[2]; IndirectPositionCollection positions = geometry.Vertices.Positions; for (int i = 0; i < positions.Count; i++) { Vector3 position = positions[i]; if (i % 3 == 2) { // Calculate the center of the triangle. triangleCenters[i / 3] = (trianglePoints[0] + trianglePoints[1] + position) / 3; } else { trianglePoints[i % 3] = position; } } // Add two new channels to the MeshContent: // triangleCenterChannel: This is the channel that will store the center // of the triangle that this vertex belongs to. // rotationalVelocityChannel: This channel has randomly generated values // for x,y and z rotational angles. This information will be used to // randomly rotate the triangles as they shatter from the model. geometry.Vertices.Channels.Add <Vector3>( triangleCenterChannel, new ReplicateTriangleDataToEachVertex <Vector3>(triangleCenters)); geometry.Vertices.Channels.Add <Vector3>( rotationalVelocityChannel, new ReplicateTriangleDataToEachVertex <Vector3>( new RandomVectorEnumerable(triangleCenters.Length))); } foreach (NodeContent child in input.Children) { finalMesh.Children.Add(ProcessMesh(child)); } return(finalMesh); }
private bool IsVectorChannel(VertexChannel vertexChannel) { return(vertexChannel.Name.StartsWith("Normal") || vertexChannel.Name.StartsWith("Tangent") || vertexChannel.Name.StartsWith("Binormal")); }
private void GetWeightChannel() { foreach (VertexChannel channel in geom.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { weightChannel = (VertexChannel<BoneWeightCollection>)channel; break; } } }
/// <summary> /// Go through the vertex channels in the geometry and replace the /// BoneWeightCollection objects with weight and index channels. /// </summary> /// <param name="geometry">The geometry to process.</param> /// <param name="vertexChannelIndex">The index of the vertex channel to process.</param> /// <param name="context">The processor context.</param> protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { bool boneCollectionsWithZeroWeights = false; if (geometry.Vertices.Channels[vertexChannelIndex].Name == VertexChannelNames.Weights()) { int meshIndex = (int)geometry.Parent.OpaqueData["MeshIndex"]; BoneIndexer indexer = indexers[meshIndex]; // Skin channels are passed in from importers as BoneWeightCollection objects VertexChannel <BoneWeightCollection> vc = (VertexChannel <BoneWeightCollection>) geometry.Vertices.Channels[vertexChannelIndex]; int maxBonesPerVertex = 0; for (int i = 0; i < vc.Count; i++) { int count = vc[i].Count; if (count > maxBonesPerVertex) { maxBonesPerVertex = count; } } // Add weights as colors (Converts well to 4 floats) // and indices as packed 4byte vectors. Color[] weightsToAdd = new Color[vc.Count]; Byte4[] indicesToAdd = new Byte4[vc.Count]; // Go through the BoneWeightCollections and create a new // weightsToAdd and indicesToAdd array for each BoneWeightCollection. for (int i = 0; i < vc.Count; i++) { BoneWeightCollection bwc = vc[i]; if (bwc.Count == 0) { boneCollectionsWithZeroWeights = true; continue; } bwc.NormalizeWeights(4); int count = bwc.Count; if (count > maxBonesPerVertex) { maxBonesPerVertex = count; } // Add the appropriate bone indices based on the bone names in the // BoneWeightCollection Vector4 bi = new Vector4(); bi.X = count > 0 ? indexer.GetBoneIndex(bwc[0].BoneName) : (byte)0; bi.Y = count > 1 ? indexer.GetBoneIndex(bwc[1].BoneName) : (byte)0; bi.Z = count > 2 ? indexer.GetBoneIndex(bwc[2].BoneName) : (byte)0; bi.W = count > 3 ? indexer.GetBoneIndex(bwc[3].BoneName) : (byte)0; indicesToAdd[i] = new Byte4(bi); Vector4 bw = new Vector4(); bw.X = count > 0 ? bwc[0].Weight : 0; bw.Y = count > 1 ? bwc[1].Weight : 0; bw.Z = count > 2 ? bwc[2].Weight : 0; bw.W = count > 3 ? bwc[3].Weight : 0; weightsToAdd[i] = new Color(bw); } // Remove the old BoneWeightCollection channel geometry.Vertices.Channels.Remove(vc); // Add the new channels geometry.Vertices.Channels.Add <Byte4>(VertexElementUsage.BlendIndices.ToString(), indicesToAdd); geometry.Vertices.Channels.Add <Color>(VertexElementUsage.BlendWeight.ToString(), weightsToAdd); } else { // No skinning info, so we let the base class process the channel base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } if (boneCollectionsWithZeroWeights) { context.Logger.LogWarning("", geometry.Identity, "BonesWeightCollections with zero weights found in geometry."); } }