public MeshContent Process() { newMesh = new MeshContent(); newMesh.Name = splitter.mesh.Name + splitter.currentIndex.ToString(); SortedDictionary<string, object> faceBones = new SortedDictionary<string, object>(); GeometryContent newGeom = new GeometryContent(); while (index < geom.Indices.Count - 1) { int[] faceIndices = new int[] { geom.Indices[index], geom.Indices[index+1], geom.Indices[index+2] }; for (int i = 0; i < 3; i++) { BoneWeightCollection weightCollection = weightChannel[ geom.Indices[index + i]]; foreach (BoneWeight weight in weightCollection) { if (!meshBones.ContainsKey(weight.BoneName) && !faceBones.ContainsKey(weight.BoneName)) faceBones.Add(weight.BoneName, null); } } if (meshBones.Count + faceBones.Count > splitter.maxBones) { faceBones.Clear(); vertexEndIndex = index; break; } foreach (string s in faceBones.Keys) meshBones.Add(s, null); faceBones.Clear(); for (int i = 0; i < 3; i++) { if (oldToNewDict.ContainsKey(faceIndices[i])) { } else { int newIndex = newMesh.Positions.Count; newMesh.Positions.Add(geom.Vertices.Positions[faceIndices[i]]); oldToNewDict.Add(faceIndices[i], newIndex); newGeom.Vertices.Add(newIndex); } newGeom.Indices.Add(oldToNewDict[faceIndices[i]]); } index += 3; vertexEndIndex = index; } newMesh.Geometry.Add(newGeom); Finish(); return newMesh; }
// Converts color channel from Vector4 to Color and premultiplies with alpha if required. private void ProcessColorChannel(GeometryContent geometry, int vertexChannelIndex) { var channels = geometry.Vertices.Channels; try { channels.ConvertChannelContent<Color>(vertexChannelIndex); } catch (NotSupportedException exception) { var channel = channels[vertexChannelIndex]; string message = String.Format( CultureInfo.InvariantCulture, "Vertex channel \"{0}\" has wrong content type. Actual type: {1}. Expected type: {2}.", channel.Name, channel.ElementType, typeof(Vector4)); throw new InvalidContentException(message, exception); } if (_modelDescription == null || _modelDescription.PremultiplyVertexColors) { var channel = channels.Get<Color>(vertexChannelIndex); for (int i = 0; i < channel.Count; i++) { Color color = channel[i]; channel[i] = Color.FromNonPremultiplied(color.R, color.G, color.B, color.A); } } }
private VertexChannel<BoneWeightCollection> GetWeightChannel(GeometryContent geom) { foreach (VertexChannel channel in geom.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { return (VertexChannel<BoneWeightCollection>)channel; } } return null; }
private void ProcessVertexChannel(GeometryContent geometry, int channelIndex) { // Get the base name of a vertex channel (e.g. "Colors" for "Colors1"). string baseName = VertexChannelNames.DecodeBaseName(geometry.Vertices.Channels[channelIndex].Name); if (baseName != null) { if (baseName == "Color") ProcessColorChannel(geometry, channelIndex); else if (baseName == "Weights") ProcessWeightsChannel(geometry, channelIndex); } }
public static MyreMeshContent CreateMyreMesh(GeometryContent geometry, Dictionary<string, MyreMaterialContent> materials) { return new MyreMeshContent { Name = geometry.Parent.Name ?? "", BoundingSphere = geometry.Vertices.Positions.Count == 0 ? new BoundingSphere(Vector3.Zero, 0) : BoundingSphere.CreateFromPoints(geometry.Vertices.Positions), Materials = materials, IndexBuffer = geometry.Indices, VertexBuffer = geometry.Vertices.CreateVertexBuffer(), VertexCount = geometry.Vertices.VertexCount, TriangleCount = geometry.Indices.Count / 3, }; }
protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { string vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name; if (acceptableVertexChannelNames.Contains(vertexChannelName)) { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } else { geometry.Vertices.Channels.Remove(vertexChannelName); } }
public MeshSplitPart( MeshSplitter splitter, GeometryContent geom, int vertexStartIndex) { this.index = vertexStartIndex; this.splitter = splitter; this.oldToNewDict = new SortedDictionary<int, int>(); this.vertexStartIndex = vertexStartIndex; this.vertexEndIndex = vertexStartIndex; this.geom = geom; GetWeightChannel(); }
protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { String vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name; // if this vertex channel has an acceptable names, process it as normal. if (m_AcceptableVertexChannelNames.Contains(vertexChannelName)) { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } // otherwise, remove it from the vertex channels; it's just extra data // we don't need. else { geometry.Vertices.Channels.Remove(vertexChannelName); } }
private static void CalculateTangentFrames(GeometryContent geometry, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName) { Debug.Assert(!string.IsNullOrWhiteSpace(textureCoordinateChannelName)); var indices = geometry.Indices; var positions = geometry.Vertices.Positions.Select(p => (Vector3F)p).ToArray(); var normals = geometry.Vertices.Channels.Get<Vector3>(VertexChannelNames.Normal()).Select(n => (Vector3F)n).ToArray(); var textureCoordinates = geometry.Vertices.Channels.Get<Vector2>(textureCoordinateChannelName).Select(n => (Vector2F)n).ToArray(); Vector3F[] tangents; Vector3F[] bitangents; DirectXMesh.ComputeTangentFrame(indices, positions, normals, textureCoordinates, out tangents, out bitangents); if (!string.IsNullOrEmpty(tangentChannelName)) geometry.Vertices.Channels.Add(tangentChannelName, tangents.Select(t => (Vector3)t)); if (!string.IsNullOrEmpty(binormalChannelName)) geometry.Vertices.Channels.Add(binormalChannelName, bitangents.Select(b => (Vector3)b)); }
/// <summary> /// Helper function creates a new geometry object, /// and sets it to use our billboard effect. /// </summary> static GeometryContent CreateVegetationGeometry(string textureFilename, float width, float height, float windAmount, ContentIdentity identity) { GeometryContent geometry = new GeometryContent(); // Add the vertex channels needed for our billboard geometry. VertexChannelCollection channels = geometry.Vertices.Channels; // Add a vertex channel holding normal vectors. channels.Add<Vector3>(VertexChannelNames.Normal(), null); // Add a vertex channel holding texture coordinates. channels.Add<Vector2>(VertexChannelNames.TextureCoordinate(0), null); // Add a second texture coordinate channel, holding a per-billboard // random number. This is used to make each billboard come out a // slightly different size, and to animate at different speeds. channels.Add<float>(VertexChannelNames.TextureCoordinate(1), null); // Create a material for rendering the billboards. EffectMaterialContent material = new EffectMaterialContent(); // Point the material at our custom billboard effect. string directory = Path.GetDirectoryName(identity.SourceFilename); string effectFilename = Path.Combine(directory, "Billboard.fx"); material.Effect = new ExternalReference<EffectContent>(effectFilename); // Set the texture to be used by these billboards. textureFilename = Path.Combine(directory, textureFilename); material.Textures.Add("Texture", new ExternalReference<TextureContent>(textureFilename)); // Set effect parameters describing the size and // wind sensitivity of these billboards. material.OpaqueData.Add("BillboardWidth", width); material.OpaqueData.Add("BillboardHeight", height); material.OpaqueData.Add("WindAmount", windAmount); geometry.Material = material; return geometry; }
/// <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(); }
private NodeContent CreateBasicMesh() { var input = new NodeContent { Name = "Root", Identity = new ContentIdentity("dummy", GetType().Name), Transform = Matrix.CreateRotationZ(MathHelper.ToRadians(60)) * Matrix.CreateRotationX(MathHelper.ToRadians(40)) * Matrix.CreateRotationY(MathHelper.ToRadians(50)), }; { var mesh = new MeshContent() { Name = "Mesh1", Transform = Matrix.Identity, }; var geom = new GeometryContent() { Name = "Geom1", }; mesh.Geometry.Add(geom); input.Children.Add(mesh); } { var mesh2 = new MeshContent() { Name = "Mesh2", Transform = Matrix.Identity, }; mesh2.Positions.Add(new Vector3(0, 0, 0)); mesh2.Positions.Add(new Vector3(1, 0, 0)); mesh2.Positions.Add(new Vector3(1, 1, 1)); var material = new BasicMaterialContent { Name = "Material1", Alpha = 0.5f, DiffuseColor = Color.Red.ToVector3(), VertexColorEnabled = true, }; var geom2 = new GeometryContent() { Name = "Geom2", Material = material, }; geom2.Vertices.Add(0); geom2.Vertices.Add(1); geom2.Vertices.Add(2); geom2.Indices.Add(0); geom2.Indices.Add(1); geom2.Indices.Add(2); mesh2.Geometry.Add(geom2); input.Children.Add(mesh2); } return input; }
public static void MergeDuplicateVertices(GeometryContent geometry) { throw new NotImplementedException(); }
private string GetExternalMaterial(MeshContent mesh, GeometryContent geometry) { if (_modelDescription != null) { var meshDescription = _modelDescription.GetMeshDescription(mesh.Name); if (meshDescription != null) { int index = mesh.Geometry.IndexOf(geometry); if (0 <= index && index < meshDescription.Submeshes.Count) return meshDescription.Submeshes[index].Material; } } // Fallback: // The model description does not define a material file. Try to use the texture name // as a fallback. if (geometry != null && geometry.Material != null && geometry.Material.Textures.ContainsKey("Texture")) { string textureFile = geometry.Material.Textures["Texture"].Filename; string materialFile = Path.ChangeExtension(textureFile, ".drmat"); if (File.Exists(materialFile)) return materialFile; } return null; }
/// <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> /// Gets or sets a value indicating whether alpha premultiply of vertex color is enabled. /// </summary> /// <value> /// <see langword="true"/> if alpha premultiply of vertex colors is enabled; otherwise, <see langword="false"/>. /// </value> [DefaultValue(true)] [DisplayName("Premultiply Vertex Colors")] [Description("If enabled, vertex color channels are converted to premultiplied alpha format.")] public virtual bool PremultiplyVertexColors { get { return _premultiplyVertexColors; } set { _premultiplyVertexColors = value; } } private bool _premultiplyVertexColors = true; */ //-------------------------------------------------------------- #endregion Other #if ANIMATION // Convert BoneWeightCollection to Byte4 (bone indices) and Vector4 (bone weights). private void ConvertBoneWeights(BoneWeightCollection boneWeightCollection, Byte4[] boneIndices, Vector4[] boneWeights, int vertexIndex, GeometryContent geometry) { // Normalize weights. (Number of weights should be MaxBonesPerVertex. Sum should be 1.) boneWeightCollection.NormalizeWeights(MaxBonesPerVertex); // Convert BoneWeights object to bone indices and bone weights. for (int i = 0; i < boneWeightCollection.Count; i++) { BoneWeight boneWeight = boneWeightCollection[i]; int boneIndex = _skeleton.GetIndex(boneWeight.BoneName); if (boneIndex == -1) { string message = String.Format( CultureInfo.InvariantCulture, "Vertex references unknown bone name \"{0}\".", boneWeight.BoneName); throw new InvalidContentException(message, geometry.Parent.Identity); } _tempIndices[i] = boneIndex; _tempWeights[i] = boneWeight.Weight; } // Clear unused indices/weights. for (int i = boneWeightCollection.Count; i < MaxBonesPerVertex; i++) { _tempIndices[i] = 0; _tempWeights[i] = 0f; } boneIndices[vertexIndex] = new Byte4(_tempIndices[0], _tempIndices[1], _tempIndices[2], _tempIndices[3]); boneWeights[vertexIndex] = new Vector4(_tempWeights[0], _tempWeights[1], _tempWeights[2], _tempWeights[3]); }
// Converts a channel of type BoneWeightCollection to two new channels: // Byte4 indices + Vector4 weights private void ProcessWeightsChannel(GeometryContent geometry, int vertexChannelIndex) { #if ANIMATION if (_skeleton == null) { // No skeleton? Remove BoneWeightCollection. geometry.Vertices.Channels.RemoveAt(vertexChannelIndex); return; } if (_skeleton.NumberOfBones > 255) { string message = String.Format( CultureInfo.InvariantCulture, "Too many bones in skeleton. Actual number of bones: {0}. Allowed number of bones: {1}.", _skeleton.NumberOfBones, 255); throw new InvalidContentException(message, _rootBone.Identity); } var channels = geometry.Vertices.Channels; var channel = channels[vertexChannelIndex]; var boneWeightChannel = channel as VertexChannel<BoneWeightCollection>; if (boneWeightChannel == null) { string message = String.Format( CultureInfo.InvariantCulture, "Vertex channel \"{0}\" has wrong content type. Actual type: {1}. Expected type: {2}.", channel.Name, channel.ElementType, typeof(BoneWeightCollection)); throw new InvalidContentException(message, geometry.Parent.Identity); } // Create two channels (Byte4 indices + Vector4 weights) from a BoneWeight channel. Byte4[] boneIndices = new Byte4[boneWeightChannel.Count]; Vector4[] boneWeights = new Vector4[boneWeightChannel.Count]; for (int i = 0; i < boneWeightChannel.Count; i++) { // Convert bone weights for vertex i. var boneWeightCollection = boneWeightChannel[i]; if (boneWeightCollection == null) { string message = String.Format( CultureInfo.InvariantCulture, "NULL entry found in channel \"{0}\". Expected element type: {1}.", boneWeightChannel.Name, typeof(BoneWeightCollection)); throw new InvalidContentException(message, geometry.Parent.Identity); } ConvertBoneWeights(boneWeightCollection, boneIndices, boneWeights, i, geometry); } // The current channel has the name "WeightsN", where N is the usage index. // Get the usage index. int usageIndex = VertexChannelNames.DecodeUsageIndex(boneWeightChannel.Name); // Store the converted bone information in two new channels called "BlendIndicesN" // and "BlendWeightsN". string blendIndices = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, usageIndex); if (channels.Contains(blendIndices)) { string message = String.Format( CultureInfo.InvariantCulture, "Cannot store converted blend indices for vertex channel \"{0}\", because a vertex channel called \"{1}\" already exists.", boneWeightChannel.Name, blendIndices); throw new InvalidContentException(message, geometry.Parent.Identity); } string blendWeights = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, usageIndex); if (channels.Contains(blendWeights)) { string message = String.Format( CultureInfo.InvariantCulture, "Cannot store converted blend weights for vertex channel \"{0}\", because a vertex channel called \"{1}\" already exists.", boneWeightChannel.Name, blendWeights); throw new InvalidContentException(message, geometry.Parent.Identity); } // Insert the new channels after "WeightsN" and remove "WeightsN". channels.Insert(vertexChannelIndex + 1, blendIndices, boneIndices); channels.Insert(vertexChannelIndex + 2, blendWeights, boneWeights); channels.RemoveAt(vertexChannelIndex); #endif }
/// <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."); }
protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { String vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name; //If this channel has an acceptable names, process it as normal. if (AcceptableVertexChannelNames.Contains(vertexChannelName)) { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } else { geometry.Vertices.Channels.Remove(vertexChannelName); } }
//Process Vertex Channel protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { //Get the Vertex Channel Name to be processed string vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name; //If this vertex channel has an acceptable name, process it as normal, else remove it if (acceptableVertexChannelNames.Contains(vertexChannelName)) { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } else { geometry.Vertices.Channels.Remove(vertexChannelName); } }
public void DefaultEffectTest() { NodeContent input; { input = new NodeContent(); var mesh = new MeshContent() { Name = "Mesh1" }; mesh.Positions.Add(new Vector3(0, 0, 0)); mesh.Positions.Add(new Vector3(1, 0, 0)); mesh.Positions.Add(new Vector3(1, 1, 1)); var geom = new GeometryContent(); geom.Vertices.Add(0); geom.Vertices.Add(1); geom.Vertices.Add(2); geom.Indices.Add(0); geom.Indices.Add(1); geom.Indices.Add(2); geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(0), new[] { new Vector2(0,0), new Vector2(1,0), new Vector2(1,1), }); var wieghts = new BoneWeightCollection(); wieghts.Add(new BoneWeight("bone1", 0.5f)); geom.Vertices.Channels.Add(VertexChannelNames.Weights(0), new[] { wieghts, wieghts, wieghts }); mesh.Geometry.Add(geom); input.Children.Add(mesh); var bone1 = new BoneContent { Name = "bone1", Transform = Matrix.CreateTranslation(0,1,0) }; input.Children.Add(bone1); var anim = new AnimationContent() { Name = "anim1", Duration = TimeSpan.Zero }; input.Animations.Add(anim.Name, anim); } var processorContext = new TestProcessorContext(TargetPlatform.Windows, "dummy.xnb"); var processor = new ModelProcessor { DefaultEffect = MaterialProcessorDefaultEffect.SkinnedEffect, }; var output = processor.Process(input, processorContext); // TODO: Not sure why, but XNA always returns a BasicMaterialContent // even when we specify SkinnedEffect as the default. We need to fix // the test first before we can enable the assert here. //Assert.IsInstanceOf(typeof(SkinnedMaterialContent), output.Meshes[0].MeshParts[0].Material); }
/// <summary> /// Helper function adds a single new billboard sprite to the output geometry. /// </summary> private void GenerateBillboard(MeshContent mesh, GeometryContent geometry, Vector3 position, Vector3 normal) { VertexContent vertices = geometry.Vertices; VertexChannelCollection channels = vertices.Channels; // First, create a vertex position entry for this billboard. Each // billboard is going to be rendered a quad, so we need to create four // vertices, but at this point we only have a single position that is // shared by all the vertices. The real position of each vertex will be // computed on the fly in the vertex shader, thus allowing us to // implement effects like making the billboard rotate to always face the // camera, and sway in the wind. As input the vertex shader only wants to // know the center point of the billboard, and that is the same for all // the vertices, so only a single position is needed here. int positionIndex = mesh.Positions.Count; mesh.Positions.Add(position); // Second, create the four vertices, all referencing the same position. int index = vertices.PositionIndices.Count; for (int i = 0; i < 4; i++) { vertices.Add(positionIndex); } // Third, add normal data for each of the four vertices. A normal for a // billboard is kind of a silly thing to define, since we are using a // 2D sprite to fake a complex 3D object that would in reality have many // different normals across its surface. Here we are just using a copy // of the normal from the ground underneath the billboard, which can be // used in our lighting computation to make the vegetation darker or // lighter depending on the lighting of the underlying landscape. VertexChannel<Vector3> normals; normals = channels.Get<Vector3>(VertexChannelNames.Normal()); for (int i = 0; i < 4; i++) { normals[index + i] = normal; } // Fourth, add texture coordinates. VertexChannel<Vector2> texCoords; texCoords = channels.Get<Vector2>(VertexChannelNames.TextureCoordinate(0)); texCoords[index + 0] = new Vector2(0, 0); texCoords[index + 1] = new Vector2(1, 0); texCoords[index + 2] = new Vector2(1, 1); texCoords[index + 3] = new Vector2(0, 1); // Fifth, add a per-billboard random value, which is the same for // all four vertices. This is used in the vertex shader to make // each billboard a slightly different size, and to be affected // differently by the wind animation. float randomValue = (float)random.NextDouble() * 2 - 1; VertexChannel<float> randomValues; randomValues = channels.Get<float>(VertexChannelNames.TextureCoordinate(1)); for (int i = 0; i < 4; i++) { randomValues[index + i] = randomValue; } // Sixth and finally, add indices defining the pair of // triangles that will be used to render the billboard. geometry.Indices.Add(index + 0); geometry.Indices.Add(index + 1); geometry.Indices.Add(index + 2); geometry.Indices.Add(index + 0); geometry.Indices.Add(index + 2); geometry.Indices.Add(index + 3); }
/// <summary> /// Determines whether the specified geometry is a skinned mesh. /// </summary> /// <param name="geometry">The <see cref="GeometryContent"/>.</param> /// <returns> /// <see langword="true"/> if <paramref name="geometry"/> is a skinned mesh; otherwise, /// <see langword="false"/>. /// </returns> public static bool IsSkinned(GeometryContent geometry) { return geometry.Vertices .Channels .OfType<VertexChannel<BoneWeightCollection>>() .Any(); }
private List<DRMorphTargetContent> BuildMorphTargets(GeometryContent geometry, List<MeshContent> inputMorphTargets, int index) { int[] vertexReorderMap = _vertexReorderMaps[index]; var morphTargets = new List<DRMorphTargetContent>(); foreach (var inputMorphTarget in inputMorphTargets) { int numberOfVertices = geometry.Vertices.VertexCount; var morphGeometry = inputMorphTarget.Geometry[index]; // Copy relative positions and normals into vertex buffer. var positions = morphGeometry.Vertices.Positions; var normals = morphGeometry.Vertices.Channels.Get<Vector3>(VertexChannelNames.Normal()); Vector3[] data = new Vector3[numberOfVertices * 2]; for (int i = 0; i < numberOfVertices; i++) { int originalIndex = vertexReorderMap[i]; data[2 * i] = positions[originalIndex]; data[2 * i + 1] = normals[originalIndex]; } // Determine if morph target is empty. bool isEmpty = true; for (int i = 0; i < data.Length; i++) { // File formats and preprocessing can introduce some inaccuracies. // --> Use a relative large epsilon. (The default Numeric.EpsilonF is too small.) const float epsilon = 1e-4f; if (!Numeric.IsZero(data[i].LengthSquared(), epsilon * epsilon)) { Debug.Write(string.Format( CultureInfo.InvariantCulture, "Morph target \"{0}\", submesh index {1}: Position/normal delta is {2}.", inputMorphTarget.Name, index, data[i].Length())); isEmpty = false; break; } } if (!isEmpty) { // (Note: VertexStride is set explicitly in CreateMorphTargetVertexBuffer().) // ReSharper disable once PossibleInvalidOperationException int vertexOffset = _morphTargetVertexBuffer.VertexData.Length / _morphTargetVertexBuffer.VertexDeclaration.VertexStride.Value; _morphTargetVertexBuffer.Write( _morphTargetVertexBuffer.VertexData.Length, 12, // The size of one Vector3 in data is 12. data); morphTargets.Add(new DRMorphTargetContent { Name = inputMorphTarget.Name, VertexBuffer = _morphTargetVertexBuffer, StartVertex = vertexOffset, }); } } return (morphTargets.Count > 0) ? morphTargets : null; }
static void ProcessGeometry(GeometryContent geometry, int parentIndex, string shapeNm, ShapeN_SkinDContent_Writing output) { // 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); } // retrieve the four vertex channels we require for CPU skinning. we ignore any // other channels the model might have. string normalNm = VertexChannelNames.EncodeName(VertexElementUsage.Normal, 0); string texCoordNm = VertexChannelNames.EncodeName(VertexElementUsage.TextureCoordinate, 0); string blendWeightNm = VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0); string blendIndexNm = VertexChannelNames.EncodeName(VertexElementUsage.BlendIndices, 0); string positionNm = VertexChannelNames.EncodeName(VertexElementUsage.Position, 0); //var tmp = geometry.Vertices.Channels[positionNm] as VertexChannel<Vector3>; VertexChannel<Vector3> normals; VertexChannel<Vector2> texCoords; VertexChannel<Vector4> blendWeights; VertexChannel<Vector4> blendIndices; //if(geometry.Vertices.Channels.Contains(normalNm)) normals = geometry.Vertices.Channels[normalNm] as VertexChannel<Vector3>; if (geometry.Vertices.Channels.Contains(texCoordNm)) texCoords = geometry.Vertices.Channels[texCoordNm] as VertexChannel<Vector2>; if (geometry.Vertices.Channels.Contains(blendWeightNm)) blendWeights = geometry.Vertices.Channels[blendWeightNm] as VertexChannel<Vector4>; if (geometry.Vertices.Channels.Contains(blendIndexNm)) blendIndices = geometry.Vertices.Channels[blendIndexNm] as VertexChannel<Vector4>; // create our array of vertices int triangleCount = geometry.Indices.Count / 3; VertexData[] verticesData = new VertexData[geometry.Vertices.VertexCount]; Vector3[] vertice = new Vector3[verticesData.Length]; for (int i = 0; i < verticesData.Length; i++) { verticesData[i] = new VertexData { Position = geometry.Vertices.Positions[i], Normal = normals[i], //TextureCoordinate = texCoords[i], //BlendWeights = blendWeights[i], //BlendIndices = blendIndices[i] }; vertice[i] = verticesData[i].Position; } BoundingSphere[] bSpheres = new BoundingSphere[1] { BoundingSphere.CreateFromPoints(vertice) }; // Add the new piece of geometry to our output model. output.SetShapeNode( shapeNm, parentIndex, triangleCount, geometry.Indices, verticesData, bSpheres); }
protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { bool isWeights = geometry.Vertices.Channels[vertexChannelIndex].Name == VertexChannelNames.Weights(); base.ProcessVertexChannel(geometry, vertexChannelIndex, context); if (isWeights) { geometry.Vertices.Channels.ConvertChannelContent<Vector4>("BlendIndices0"); geometry.Vertices.Channels.ConvertChannelContent<Vector4>("BlendWeight0"); } }
/// <summary> /// Constructs a VertexContent instance. /// </summary> internal VertexContent(GeometryContent geom) { positionIndices = new VertexChannel<int>("PositionIndices"); positions = new IndirectPositionCollection(geom, positionIndices); channels = new VertexChannelCollection(this); }
public static MeshContent BuildMesh(MMDModel1 model, string filename) { //メッシュ作成 MeshContent buildingMesh = new MeshContent(); //まずは頂点を登録。 //ひげねこ氏によるとモデルごとのローカル座標である必要があるようだが //pmdはモデル一つ=変換必要なし foreach (var vec in model.Vertexes) buildingMesh.Positions.Add(MMDXMath.ToVector3(vec.Pos)); //ジオメトリとマテリアルの作成 //メモ:頂点が3つ合わさって面、面が幾つか集まってジオメトリ。ジオメトリにマテリアルが付随。ジオメトリの集合がメッシュ long FaceIndex = 0; Dictionary<ushort, int> vertMap = new Dictionary<ushort, int>(); //ジオメトリとマテリアルの生成 for (int i = 0; i < model.Materials.Length; i++) { GeometryContent geometry = new GeometryContent(); BasicMaterialContent material = new BasicMaterialContent(); geometry.Material = material; //マテリアル設定 material.VertexColorEnabled = false;//頂点カラー無し material.Alpha = model.Materials[i].Alpha; material.DiffuseColor = MMDXMath.ToVector3(model.Materials[i].DiffuseColor); material.EmissiveColor = MMDXMath.ToVector3(model.Materials[i].MirrorColor); material.SpecularColor = MMDXMath.ToVector3(model.Materials[i].SpecularColor); material.SpecularPower = model.Materials[i].Specularity; if (!string.IsNullOrEmpty(model.Materials[i].TextureFileName)) material.Texture = new ExternalReference<TextureContent>(NormalizeFilepath(model.Materials[i].TextureFileName, filename)); if (!string.IsNullOrEmpty(model.Materials[i].SphereTextureFileName)) { if (Path.GetExtension(model.Materials[i].SphereTextureFileName).ToLower() == ".sph") { material.OpaqueData.Add("UseSphere", 1); } else if (Path.GetExtension(model.Materials[i].SphereTextureFileName).ToLower() == ".spa") { material.OpaqueData.Add("UseSphere", 2); } else throw new InvalidContentException("スフィアマップは*.sph, *.spaのみ指定可能です: " + model.Materials[i].SphereTextureFileName); material.Textures.Add("Sphere", new ExternalReference<TextureContent>(ProcessSphere(NormalizeFilepath(model.Materials[i].SphereTextureFileName, filename)))); } else { material.OpaqueData.Add("UseSphere", 0); } //トゥーンのテクスチャを入れる string toonTexPath = ToonTexManager.Instance.GetToonTexPath(model.Materials[i].ToonIndex, model.ToonFileNames, filename); if (!string.IsNullOrEmpty(toonTexPath)) { material.Textures.Add("ToonTex", new ExternalReference<TextureContent>(toonTexPath)); material.OpaqueData.Add("UseToon", true); } else material.OpaqueData.Add("UseToon", false); //一応エッジ情報突っ込んでおく material.OpaqueData.Add("Edge", (model.Materials[i].EdgeFlag != 0)); //ジオメトリのチャンネル設定 //法線 geometry.Vertices.Channels.Add(VertexChannelNames.Normal(0), typeof(Vector3), null); //テクスチャ if (!string.IsNullOrEmpty(model.Materials[i].TextureFileName)) geometry.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(0), typeof(Vector2), null); //ボーンウェイト geometry.Vertices.Channels.Add(VertexChannelNames.Weights(0), typeof(BoneWeightCollection), null); //面と頂点をジオメトリに登録 //このマテリアルに対応する面は今までのマテリアルの面数の合計からこのマテリアルの面数個分 vertMap.Clear(); //マテリアルに付随する面を取得 for (long j = FaceIndex; j < FaceIndex + model.Materials[i].FaceVertCount; j++) { //面から頂点番号取得 ushort VertIndex = model.FaceVertexes[j]; //ジオメトリに登録済みかどうか? int geoVertIndex; if (!vertMap.TryGetValue(VertIndex, out geoVertIndex)) { //未登録なので、ジオメトリに登録し、ジオメトリ頂点番号取得 geoVertIndex = geometry.Vertices.Add(VertIndex); //頂点マップに登録 vertMap.Add(VertIndex, geoVertIndex); //チャンネル情報の登録 int channelIndex = 0; //法線登録 geometry.Vertices.Channels.Get<Vector3>(channelIndex++)[geoVertIndex] = MMDXMath.ToVector3(model.Vertexes[VertIndex].NormalVector); //テクスチャ座標 if (!string.IsNullOrEmpty(model.Materials[i].TextureFileName)) geometry.Vertices.Channels.Get<Vector2>(channelIndex++)[geoVertIndex] = MMDXMath.ToVector2(model.Vertexes[VertIndex].UV); //ボーンウェイト BoneWeightCollection boneWeight = new BoneWeightCollection(); int boneNum = model.Vertexes[VertIndex].BoneNum[0]; if (boneNum >= 0 && boneNum < model.Bones.Length) boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, model.Vertexes[VertIndex].BoneWeight / 100f)); boneNum = model.Vertexes[VertIndex].BoneNum[1]; if (boneNum >= 0 && boneNum < model.Bones.Length) boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, 1.0f - model.Vertexes[VertIndex].BoneWeight / 100f)); geometry.Vertices.Channels.Get<BoneWeightCollection>(channelIndex++)[geoVertIndex] = boneWeight; } //インデックスに登録 geometry.Indices.Add(geoVertIndex); } //ジオメトリをモデルに追加 buildingMesh.Geometry.Add(geometry); //面頂点カウント進める FaceIndex += model.Materials[i].FaceVertCount; } //重複頂点データのマージ //MeshHelper.MergeDuplicatePositions(buildingMesh, 0); //MeshHelper.MergeDuplicateVertices(buildingMesh); //メッシュ出来たので返却 return buildingMesh; }
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); }
private void Split(GeometryContent geom) { int vertexStart = 0; MeshSplitPart part; while (vertexStart < geom.Indices.Count) { part = new MeshSplitPart(this, geom, vertexStart); currentIndex++; MeshContent newMesh = part.Process(); vertexStart = part.VertexEndIndex; newMesh.Transform = mesh.Transform; meshes.Add(newMesh); } }
/// <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) { // Look for an existing normals channel. var channel = geom.Vertices.Channels.Get<Vector3>(VertexChannelNames.Normal()); if (channel == null) { // 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; } 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[positionIndices[i]]; }
internal VertexContent(GeometryContent parent) { this.positionIndices = new VertexChannel<int>("PositionIndices"); this.positions = new IndirectPositionCollection(parent, this.positionIndices); this.channels = new VertexChannelCollection(this); }