/// <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); } }
/// <summary> /// Converts a single piece of input geometry into our instanced format. /// </summary> void ProcessGeometry(GeometryContent geometry) { int indexCount = geometry.Indices.Count; int vertexCount = geometry.Vertices.VertexCount; // Validate that the number of vertices is suitable for instancing. if (vertexCount > ushort.MaxValue) { throw new InvalidContentException( string.Format("Geometry contains {0} vertices: " + "this is too many to be instanced.", vertexCount)); } if (vertexCount > ushort.MaxValue / 8) { context.Logger.LogWarning(null, rootNode.Identity, "Geometry contains {0} vertices: " + "this will only allow it to be instanced " + "{1} times per batch. A model with fewer " + "vertices would be more efficient.", vertexCount, ushort.MaxValue / vertexCount); } // Validate that the vertex channels we are going to use to pass // through our instancing data aren't already in use. VertexChannelCollection vertexChannels = geometry.Vertices.Channels; for (int i = 1; i <= 4; i++) { if (vertexChannels.Contains(VertexChannelNames.TextureCoordinate(i))) { throw new InvalidContentException( string.Format("Model already contains data for texture " + "coordinate channel {0}, but instancing " + "requires this channel for its own use.", i)); } } // Flatten the flexible input vertex channel data into // a simple GPU style vertex buffer byte array. VertexBufferContent vertexBufferContent; VertexElement[] vertexElements; geometry.Vertices.CreateVertexBuffer(out vertexBufferContent, out vertexElements, context.TargetPlatform); int vertexStride = VertexDeclaration.GetVertexStrideSize(vertexElements, 0); // Convert the input material. MaterialContent material = ProcessMaterial(geometry.Material); // Add the new piece of geometry to our output model. outputModel.AddModelPart(indexCount, vertexCount, vertexStride, vertexElements, vertexBufferContent, geometry.Indices, material); }
private static void ProcessColorChannel(GeometryContent geometry, int vertexChannelIndex) { VertexChannelCollection channels = geometry.Vertices.Channels; try { channels.ConvertChannelContent <Color>(vertexChannelIndex); } catch (NotSupportedException) { throw new InvalidCastException("Unable to convert mesh embedded colour channel to Vector4"); } }
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; }
/// <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); }
//funnily enough, Weights and colours screw up VertexContent.CreateVertexBuffer() //so the normal XNA model importer hacks around this... which is wonderful. //this is an exact copy of the hack private void ProcessVertexChannels(GeometryContent geometry, ContentProcessorContext context, string asset, Dictionary <string, int> boneIndices, Dictionary <int, int> boneRemap) { VertexChannelCollection collection = geometry.Vertices.Channels; List <VertexChannel> list = new List <VertexChannel>(collection); int vertexChannelIndex = 0; foreach (VertexChannel channel in list) { if (((vertexChannelIndex < 0) || (vertexChannelIndex >= collection.Count)) || (collection[vertexChannelIndex] != channel)) { vertexChannelIndex = collection.IndexOf(channel); if (vertexChannelIndex < 0) { continue; } } this.ProcessVertexChannel(geometry, vertexChannelIndex, context, asset, boneIndices, boneRemap); vertexChannelIndex++; } }
/// <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> /// 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); }
} // Process #endregion #region Process Vertex Channel /// <summary> /// Processes geometry content vertex channels at the specified index. /// </summary> protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { // Compressed Vertex Data VertexChannelCollection channels = geometry.Vertices.Channels; string name = channels[vertexChannelIndex].Name; if (name == VertexChannelNames.Normal()) { channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex); } else if (name == VertexChannelNames.TextureCoordinate(0)) { // If the resource has texture coordinates outside the range [-1, 1] the values will be clamped. channels.ConvertChannelContent <HalfVector2>(vertexChannelIndex); } else if (name == VertexChannelNames.TextureCoordinate(1)) { channels.Remove(VertexChannelNames.TextureCoordinate(1)); } else if (name == VertexChannelNames.TextureCoordinate(2)) { channels.Remove(VertexChannelNames.TextureCoordinate(2)); } else if (name == VertexChannelNames.TextureCoordinate(3)) { channels.Remove(VertexChannelNames.TextureCoordinate(3)); } else if (name == VertexChannelNames.TextureCoordinate(4)) { channels.Remove(VertexChannelNames.TextureCoordinate(4)); } else if (name == VertexChannelNames.TextureCoordinate(5)) { channels.Remove(VertexChannelNames.TextureCoordinate(5)); } else if (name == VertexChannelNames.TextureCoordinate(6)) { channels.Remove(VertexChannelNames.TextureCoordinate(6)); } else if (name == VertexChannelNames.TextureCoordinate(7)) { channels.Remove(VertexChannelNames.TextureCoordinate(7)); } else if (name == VertexChannelNames.Color(0)) { channels.Remove(VertexChannelNames.Color(0)); } else if (name == VertexChannelNames.Tangent(0)) { channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex); } else if (name == VertexChannelNames.Binormal(0)) { // Not need to get rid of the binormal data because the model will use more than 32 bytes per vertex. // We can actually try to align the data to 64 bytes per vertex. channels.ConvertChannelContent <NormalizedShort4>(vertexChannelIndex); } else { // Blend indices, blend weights and everything else. // Don't use "BlendWeight0" as a name, nor weights0. Both names don't work. base.ProcessVertexChannel(geometry, vertexChannelIndex, context); channels.ConvertChannelContent <Byte4>("BlendIndices0"); channels.ConvertChannelContent <NormalizedShort4>(VertexChannelNames.EncodeName(VertexElementUsage.BlendWeight, 0)); } } // ProcessVertexChannel
} // Process #endregion #region Process Vertex Channel /// <summary> /// Processes geometry content vertex channels at the specified index. /// </summary> protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { VertexChannelCollection channels = geometry.Vertices.Channels; // If the model has only position and normals a UV channel is added. // http://xnafinalengine.codeplex.com/wikipage?title=Compressed%20Vertex%20Data if (channels.Count == 1 && channels.Contains(VertexChannelNames.Normal())) { channels.Add<Vector2>(VertexChannelNames.TextureCoordinate(0), null); } // If the model has position, normal and UV then the data is packed on 32 bytes aliagned vertex data. if (channels.Count == 2 && channels.Contains(VertexChannelNames.Normal()) && channels.Contains(VertexChannelNames.TextureCoordinate(0))) { // No compressed Vertex Data base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } else // If not then the data is compressed. { string name = channels[vertexChannelIndex].Name; if (name == VertexChannelNames.Normal()) { channels.ConvertChannelContent<NormalizedShort4>(vertexChannelIndex); } else if (name == VertexChannelNames.TextureCoordinate(0)) { // Clamp values. /*for (int i = 0; i < channels[vertexChannelIndex].Count; i++) { Vector2 uv = (Vector2)channels[vertexChannelIndex][i]; if (uv.X < 0) uv.X *= -1; if (uv.Y < 0) uv.Y *= -1; Vector2 uvCampled = new Vector2(uv.X - (float)Math.Truncate(uv.X), uv.Y - (float)Math.Truncate(uv.Y)); channels[vertexChannelIndex][i] = uvCampled; } // If the resource has texture coordinates outside the range [-1, 1] the values will be clamped. channels.ConvertChannelContent<NormalizedShort2>(vertexChannelIndex);*/ // Sometimes you can't just clamp values, because the distance between vertices surpass 1 uv unit. // And given that I am not removing the binormals I won't normalize the UVs. channels.ConvertChannelContent<HalfVector2>(vertexChannelIndex); } else if (name == VertexChannelNames.TextureCoordinate(1)) channels.Remove(VertexChannelNames.TextureCoordinate(1)); else if (name == VertexChannelNames.TextureCoordinate(2)) channels.Remove(VertexChannelNames.TextureCoordinate(2)); else if (name == VertexChannelNames.TextureCoordinate(3)) channels.Remove(VertexChannelNames.TextureCoordinate(3)); else if (name == VertexChannelNames.TextureCoordinate(4)) channels.Remove(VertexChannelNames.TextureCoordinate(4)); else if (name == VertexChannelNames.TextureCoordinate(5)) channels.Remove(VertexChannelNames.TextureCoordinate(5)); else if (name == VertexChannelNames.TextureCoordinate(6)) channels.Remove(VertexChannelNames.TextureCoordinate(6)); else if (name == VertexChannelNames.TextureCoordinate(7)) channels.Remove(VertexChannelNames.TextureCoordinate(7)); else if (name == VertexChannelNames.Color(0)) channels.Remove(VertexChannelNames.Color(0)); else if (name == VertexChannelNames.Tangent(0)) { channels.ConvertChannelContent<NormalizedShort4>(vertexChannelIndex); } else if (name == VertexChannelNames.Binormal(0)) { channels.ConvertChannelContent<NormalizedShort4>(vertexChannelIndex); // If the binormal is removed then the position, the normal, // the tangent and one texture coordinate can be fetched in one single block of 32 bytes. // Still, it is more fast to just pass the value. At least on the test I made. //channels.Remove(VertexChannelNames.Binormal(0)); } else { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } } } // ProcessVertexChannel
/// <summary> /// Constructs a VertexContent instance. /// </summary> internal VertexContent() { channels = new VertexChannelCollection(this); positionIndices = new VertexChannel<int>("PositionIndices"); positions = new IndirectPositionCollection(); }