public HeightMapInfoContent(ModelContent model, MeshContent terrainMesh, float terrainScale, int terrainWidth, int terrainLength) { Model = model; if (terrainMesh == null) { throw new ArgumentNullException("terrainMesh"); } if (terrainWidth <= 0) { throw new ArgumentOutOfRangeException("terrainWidth"); } if (terrainLength <= 0) { throw new ArgumentOutOfRangeException("terrainLength"); } TerrainScale = terrainScale; Height = new float[terrainWidth, terrainLength]; Normals = new Vector3[terrainWidth, terrainLength]; GeometryContent item = terrainMesh.Geometry[0]; for (int i = 0; i < item.Vertices.VertexCount; i++) { Vector3 vector3 = item.Vertices.Positions[i]; Vector3 item1 = (Vector3)item.Vertices.Channels[VertexChannelNames.Normal()][i]; int x = (int)(vector3.X / terrainScale + (terrainWidth - 1) / 2f); int z = (int)(vector3.Z / terrainScale + (terrainLength - 1) / 2f); Height[x, z] = vector3.Y; Normals[x, z] = item1; } }
public static string GetXNAName(VertexAttribute attr) { switch (attr.usage) { case COLOR: return(VertexChannelNames.Color(0)); case NORMAL: return(VertexChannelNames.Normal()); case TEX_COORD: return(VertexChannelNames.TextureCoordinate(attr.attrIndex)); case BONE_WEIGHT: return(VertexChannelNames.Weights(attr.attrIndex)); case TANGENT: return(VertexChannelNames.Tangent(0)); case BINORMAL: return(VertexChannelNames.Binormal(0)); } return(null); }
public void BasicMeshBuilderTest() { var output = CreateBasicMesh(material1); Assert.NotNull(output); Assert.NotNull(output.Geometry); Assert.NotNull(output.Positions); Assert.AreEqual(new Vector3(0, 0, 0), output.Positions[0]); Assert.AreEqual(new Vector3(1, 0, 0), output.Positions[1]); Assert.AreEqual(new Vector3(1, 1, 1), output.Positions[2]); Assert.AreEqual(1, output.Geometry.Count); Assert.AreEqual(0, output.Geometry[0].Indices[0]); Assert.AreEqual(1, output.Geometry[0].Indices[1]); Assert.AreEqual(2, output.Geometry[0].Indices[2]); Assert.AreEqual(0, output.Geometry[0].Vertices.PositionIndices[0]); Assert.AreEqual(1, output.Geometry[0].Vertices.PositionIndices[1]); Assert.AreEqual(2, output.Geometry[0].Vertices.PositionIndices[2]); Assert.AreEqual(new Vector3(0, 0, 0), output.Geometry[0].Vertices.Positions[0]); Assert.AreEqual(new Vector3(1, 0, 0), output.Geometry[0].Vertices.Positions[1]); Assert.AreEqual(new Vector3(1, 1, 1), output.Geometry[0].Vertices.Positions[2]); //Check if normals are generated Assert.NotNull(output.Geometry[0].Vertices.Channels[VertexChannelNames.Normal(0)]); Assert.AreEqual(material1, output.Geometry[0].Material); Assert.AreEqual(Matrix.Identity, output.Transform); Assert.AreEqual(3, output.Positions.Count); Assert.AreEqual("Mesh1", output.Name); }
void startMesh(string name) { meshBuilder = MeshBuilder.StartMesh(name); meshBuilder.SwapWindingOrder = true; textureCoordinateDataIndex = meshBuilder.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(0)); normalDataIndex = meshBuilder.CreateVertexChannel <Vector3>(VertexChannelNames.Normal( )); positionMap = new int[positions.Count]; for (int i = 0; i < positions.Count; i++) { positionMap[i] = meshBuilder.CreatePosition(positions[i]); } }
/// <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> /// This constructor will initialize the height array from the values in the /// bitmap. Each pixel in the bitmap corresponds to one entry in the height /// array. /// </summary> public HeightMapInfoContent(MeshContent terrainMesh, float terrainScale, int terrainWidth, int terrainLength) { // validate the parameters if (terrainMesh == null) { throw new ArgumentNullException("terrainMesh"); } if (terrainWidth <= 0) { throw new ArgumentOutOfRangeException("terrainWidth"); } if (terrainLength <= 0) { throw new ArgumentOutOfRangeException("terrainLength"); } this.terrainScale = terrainScale; // create new arrays of the requested size. height = new float[terrainWidth, terrainLength]; normals = new Vector3[terrainWidth, terrainLength]; // to fill those arrays, we'll look at the position and normal data // contained in the terrainMesh. GeometryContent geometry = terrainMesh.Geometry[0]; // we'll go through each vertex.... for (int i = 0; i < geometry.Vertices.VertexCount; i++) { // ... and look up its position and normal. Vector3 position = geometry.Vertices.Positions[i]; Vector3 normal = (Vector3)geometry.Vertices.Channels [VertexChannelNames.Normal()][i]; // from the position's X and Z value, we can tell what X and Y // coordinate of the arrays to put the height and normal into. int arrayX = (int) ((position.X / terrainScale) + (terrainWidth - 1) / 2.0f); int arrayY = (int) ((position.Z / terrainScale) + (terrainLength - 1) / 2.0f); height[arrayX, arrayY] = position.Y; normals[arrayX, arrayY] = normal; } }
private static void CalculateNormals(MeshContent mesh, bool overwriteExistingNormals) { Debug.Assert(mesh != null); string name = VertexChannelNames.Normal(); if (!overwriteExistingNormals && mesh.Geometry.All(geometry => geometry.Vertices.Channels.Contains(name))) { return; // Nothing to do. } // Accumulate triangle normals at vertices. // IMPORTANT: Calculating normals per submesh does not work! // - Normals at submesh borders are only correct if we consider adjacent submeshes. // - GeometryContent.Positions contains duplicated entries if neighbor triangles do // have the same texture coordinates. MeshContent.Positions are unique (no duplicates). var positions = mesh.Positions.Select(p => (Vector3F)p).ToArray(); var indices = mesh.Geometry .SelectMany(geometry => geometry.Indices.Select(i => geometry.Vertices.PositionIndices[i])) .ToArray(); // Calculate vertex normals. var normals = DirectXMesh.ComputeNormals(indices, positions, true, VertexNormalAlgorithm.WeightedByAngle); // Copy normals to vertex channels. foreach (var geometry in mesh.Geometry) { if (!geometry.Vertices.Channels.Contains(name)) { geometry.Vertices.Channels.Add <Vector3>(name, null); } else if (!overwriteExistingNormals) { continue; } var normalChannel = geometry.Vertices.Channels.Get <Vector3>(name); var positionIndices = geometry.Vertices.PositionIndices; for (int i = 0; i < normalChannel.Count; i++) { normalChannel[i] = (Vector3)normals[positionIndices[i]]; } } }
/// <summary> /// Starts a new mesh and fills it with mesh mapped positions. /// </summary> /// <param name="name">Name of mesh.</param> private void StartMesh(string name) { meshBuilder = MeshBuilder.StartMesh(name); // Obj files need their winding orders swapped meshBuilder.SwapWindingOrder = true; // Add additional vertex channels for texture coordinates and normals textureCoordinateDataIndex = meshBuilder.CreateVertexChannel <Vector2>( VertexChannelNames.TextureCoordinate(0)); normalDataIndex = meshBuilder.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); // Add each position to this mesh with CreatePosition positionMap = new int[positions.Count]; for (int i = 0; i < positions.Count; i++) { // positionsMap redirects from the original positions in the order // they were read from file to indices returned from CreatePosition positionMap[i] = meshBuilder.CreatePosition(positions[i]); } }
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)); } }
private GeometryContent CreateGeometry(MeshContent mesh, Mesh aiMesh) { var geom = new GeometryContent { Material = _materials[aiMesh.MaterialIndex] }; // Vertices var baseVertex = mesh.Positions.Count; foreach (var vert in aiMesh.Vertices) { mesh.Positions.Add(ToXna(vert)); } geom.Vertices.AddRange(Enumerable.Range(baseVertex, aiMesh.VertexCount)); geom.Indices.AddRange(aiMesh.GetIndices()); if (aiMesh.HasBones) { var xnaWeights = new List <BoneWeightCollection>(); for (var i = 0; i < geom.Indices.Count; i++) { var list = new BoneWeightCollection(); for (var boneIndex = 0; boneIndex < aiMesh.BoneCount; boneIndex++) { var bone = aiMesh.Bones[boneIndex]; foreach (var weight in bone.VertexWeights) { if (weight.VertexID != i) { continue; } list.Add(new BoneWeight(bone.Name, weight.Weight)); } } if (list.Count > 0) { xnaWeights.Add(list); } } geom.Vertices.Channels.Add(VertexChannelNames.Weights(0), xnaWeights); } // Individual channels go here if (aiMesh.HasNormals) { geom.Vertices.Channels.Add(VertexChannelNames.Normal(), ToXna(aiMesh.Normals)); } for (var i = 0; i < aiMesh.TextureCoordinateChannelCount; i++) { geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(i), ToXnaTexCoord(aiMesh.TextureCoordinateChannels[i])); } for (var i = 0; i < aiMesh.VertexColorChannelCount; i++) { geom.Vertices.Channels.Add(VertexChannelNames.Color(i), ToXnaColors(aiMesh.VertexColorChannels[i])); } return(geom); }
/// <summary> /// Recursive function adds vegetation billboards to all meshes. /// </summary> void GenerateVegetation(NodeContent node, ContentIdentity identity) { // First, recurse over any child nodes. foreach (NodeContent child in node.Children) { GenerateVegetation(child, identity); } // Check whether this node is in fact a mesh. MeshContent mesh = node as MeshContent; if (mesh != null) { // Create three new geometry objects, one for each type // of billboard that we are going to create. Set different // effect parameters to control the size and wind sensitivity // for each type of billboard. GeometryContent grass = CreateVegetationGeometry("grass.tga", 5, 5, 1, identity); GeometryContent trees = CreateVegetationGeometry("tree.tga", 12, 12, 0.5f, identity); GeometryContent cats = CreateVegetationGeometry("cat.tga", 5, 5, 0, identity); MeshContent vegetationMesh = new MeshContent { Name = "Billboards" }; // Loop over all the existing geometry in this mesh. foreach (GeometryContent geometry in mesh.Geometry) { IList <int> indices = geometry.Indices; IList <Vector3> positions = geometry.Vertices.Positions; IList <Vector3> normals = geometry.Vertices.Channels.Get <Vector3>(VertexChannelNames.Normal()); // Loop over all the triangles in this piece of geometry. for (int triangle = 0; triangle < indices.Count; triangle += 3) { // Look up the three indices for this triangle. int i1 = indices[triangle]; int i2 = indices[triangle + 1]; int i3 = indices[triangle + 2]; // Create vegetation billboards to cover this triangle. // A more sophisticated implementation would measure the // size of the triangle to work out how many to create, // but we don't bother since we happen to know that all // our triangles are roughly the same size. for (int count = 0; count < BillboardsPerTriangle; count++) { Vector3 position, normal; // Choose a random location on the triangle. PickRandomPoint(positions[i1], positions[i2], positions[i3], normals[i1], normals[i2], normals[i3], out position, out normal); // Randomly choose what type of billboard to create. GeometryContent billboardType; if (random.NextDouble() < TreeProbability) { billboardType = trees; // As a special case, force trees to point straight // upward, even if they are growing on a slope. // That's what trees do in real life, after all! normal = Vector3.Up; } else if (random.NextDouble() < CatProbability) { billboardType = cats; } else { billboardType = grass; } // Add a new billboard to the output geometry. GenerateBillboard(vegetationMesh, billboardType, position, normal); } } } // Add our new billboard geometry to the main mesh. vegetationMesh.Geometry.Add(grass); vegetationMesh.Geometry.Add(trees); vegetationMesh.Geometry.Add(cats); mesh.Children.Add(vegetationMesh); } }
/// <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); }
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); }
private ModelMeshContent ProcessMesh(MeshContent mesh, ModelBoneContent parent, ContentProcessorContext context) { var parts = new List <ModelMeshPartContent>(); var vertexBuffer = new VertexBufferContent(); var indexBuffer = new IndexCollection(); if (GenerateTangentFrames) { context.Logger.LogMessage("Generating tangent frames."); foreach (GeometryContent geom in mesh.Geometry) { if (!geom.Vertices.Channels.Contains(VertexChannelNames.Normal(0))) { MeshHelper.CalculateNormals(geom, true); } if (!geom.Vertices.Channels.Contains(VertexChannelNames.Tangent(0)) || !geom.Vertices.Channels.Contains(VertexChannelNames.Binormal(0))) { MeshHelper.CalculateTangentFrames(geom, VertexChannelNames.TextureCoordinate(0), VertexChannelNames.Tangent(0), VertexChannelNames.Binormal(0)); } } } var startVertex = 0; foreach (var geometry in mesh.Geometry) { var vertices = geometry.Vertices; var vertexCount = vertices.VertexCount; ModelMeshPartContent partContent; if (vertexCount == 0) { partContent = new ModelMeshPartContent(); } else { var geomBuffer = geometry.Vertices.CreateVertexBuffer(); vertexBuffer.Write(vertexBuffer.VertexData.Length, 1, geomBuffer.VertexData); var startIndex = indexBuffer.Count; indexBuffer.AddRange(geometry.Indices); partContent = new ModelMeshPartContent(vertexBuffer, indexBuffer, startVertex, vertexCount, startIndex, geometry.Indices.Count / 3); // Geoms are supposed to all have the same decl, so just steal one of these vertexBuffer.VertexDeclaration = geomBuffer.VertexDeclaration; startVertex += vertexCount; } partContent.Material = geometry.Material; parts.Add(partContent); } var bounds = new BoundingSphere(); if (mesh.Positions.Count > 0) { bounds = BoundingSphere.CreateFromPoints(mesh.Positions); } return(new ModelMeshContent(mesh.Name, mesh, parent, bounds, parts)); }
public override NodeContent Import(string filename, ContentImporterContext context) { var content = new NodeContent(); var reader = XmlReader.Create(filename); var xmlMesh = (XmlMesh) new XmlSerializer(typeof(XmlMesh)).Deserialize(reader); reader.Close(); reader = XmlReader.Create(Path.Combine(Path.GetDirectoryName(filename), xmlMesh.SkeletonLink.Name + ".xml")); var xmlSkeleton = (XmlSkeleton) new XmlSerializer(typeof(XmlSkeleton)).Deserialize(reader); reader.Close(); context.Logger.LogImportantMessage("Bones: " + xmlSkeleton.Bones.Length.ToString()); var bones = new Dictionary <string, BoneContent>(); foreach (var xmlBone in xmlSkeleton.Bones) { context.Logger.LogImportantMessage("{0}", "-- " + xmlBone.Name + ": " + xmlBone.Position.AsVector3().ToString() + ", " + xmlBone.Rotation.Angle.ToString() + "/" + xmlBone.Rotation.Axis.AsVector3().ToString()); var boneContent = new BoneContent() { Name = xmlBone.Name, Transform = Matrix.CreateFromAxisAngle(xmlBone.Rotation.Axis.AsVector3(), xmlBone.Rotation.Angle) * Matrix.CreateTranslation(xmlBone.Position.AsVector3()) }; bones.Add(xmlBone.Name, boneContent); } foreach (var boneParent in xmlSkeleton.BoneParents) { var parent = bones[boneParent.Parent]; var bone = bones[boneParent.Bone]; parent.Children.Add(bone); } var rootBone = bones.Single(x => x.Value.Parent == null); content.Children.Add(rootBone.Value); context.Logger.LogImportantMessage("Submeshes: " + xmlMesh.SubMeshes.Length.ToString()); //context.AddDependency(Path.GetFullPath("HUM_M.MATERIAL")); //var materialFile = File.ReadAllText("HUM_M.MATERIAL"); ////context.Logger.LogImportantMessage("{0}", materialFile); foreach (var xmlSubMesh in xmlMesh.SubMeshes) { context.Logger.LogImportantMessage("Submesh: " + xmlSubMesh.Material); context.Logger.LogImportantMessage("-- Faces: " + xmlSubMesh.Faces.Length.ToString()); if (xmlSubMesh.UseSharedGeometry) { context.Logger.LogImportantMessage("-- Uses Shared Geometry"); } else { context.Logger.LogImportantMessage("-- Vertexbuffers: " + xmlSubMesh.Geometry.VertexBuffers.Length.ToString()); context.Logger.LogImportantMessage("-- Vertices (0): " + xmlSubMesh.Geometry.VertexBuffers[0].Vertices.Length.ToString()); context.Logger.LogImportantMessage("-- Vertices (1): " + xmlSubMesh.Geometry.VertexBuffers[1].Vertices.Length.ToString()); } var builder = MeshBuilder.StartMesh(xmlSubMesh.Material); //if (xmlSubMesh.Material == "Hum_M/Chest") // builder.SetMaterial(new SkinnedMaterialContent { Texture = new ExternalReference<TextureContent>("TL2_ARMORTEST_CHEST.png") }); //else if (xmlSubMesh.Material == "Hum_M/MidLeg") // builder.SetMaterial(new SkinnedMaterialContent { Texture = new ExternalReference<TextureContent>("TL2_ARMORTEST_PANTS.png") }); //else builder.SetMaterial(new SkinnedMaterialContent { Texture = new ExternalReference <TextureContent>("Fiend\\FIEND.dds") }); var normalChannel = builder.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); var uvChannel = builder.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(0)); var weightsChannel = builder.CreateVertexChannel <BoneWeightCollection>(VertexChannelNames.Weights()); var geometry = xmlSubMesh.Geometry; if (xmlSubMesh.UseSharedGeometry) { geometry = xmlMesh.SharedGeometry; } foreach (var vertex in geometry.VertexBuffers[0].Vertices) { builder.CreatePosition(vertex.Position.AsVector3()); } foreach (var face in xmlSubMesh.Faces) { AddTriangleVertex(builder, xmlMesh, xmlSubMesh, xmlSkeleton, face.Vertex1, normalChannel, uvChannel, weightsChannel); AddTriangleVertex(builder, xmlMesh, xmlSubMesh, xmlSkeleton, face.Vertex2, normalChannel, uvChannel, weightsChannel); AddTriangleVertex(builder, xmlMesh, xmlSubMesh, xmlSkeleton, face.Vertex3, normalChannel, uvChannel, weightsChannel); } content.Children.Add(builder.FinishMesh()); } return(content); }
internal Mesh(PopulatedStructure mesh, BlenderFile file) { FileBlock materialArray; int pointerSize = mesh["mat"].Size; Name = new string(mesh["id.name"].GetValueAsCharArray()).Split('\0')[0].Substring(2); ulong mat = mesh["mat"].GetValueAsPointer(); if (mat == 0 || (materialArray = file.GetBlockByAddress(mat)).Size % pointerSize != 0) { Materials = new Material[0]; } else { int count = materialArray.Size % pointerSize; Materials = new Material[count]; for (int i = 0; i < count; i++) { Materials[i] = Material.GetOrCreateMaterial( file, file.GetStructuresByAddress( pointerSize == 4 ? BitConverter.ToUInt32(materialArray.Data, count * pointerSize) : BitConverter.ToUInt64(materialArray.Data, count * pointerSize) )[0] ); } } float[] vectorTemp = mesh["loc"].GetValueAsFloatArray(); Location = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); vectorTemp = mesh["rot"].GetValueAsFloatArray(); Rotation = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); vectorTemp = mesh["size"].GetValueAsFloatArray(); Size = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); MeshBuilder primordialMesh = MeshBuilder.StartMesh(Name); // both structures use the same vertex structure List <Vector3> verts = new List <Vector3>(); List <short[]> unconvertedNormals = new List <short[]>(); foreach (PopulatedStructure s in file.GetStructuresByAddress(mesh["mvert"].GetValueAsPointer())) { float[] vector = s["co"].GetValueAsFloatArray(); unconvertedNormals.Add(s["no"].GetValueAsShortArray()); verts.Add(new Vector3(vector[0], vector[1], vector[2])); } List <Vector3> normals = convertNormals(unconvertedNormals); VertexPositionNormalTexture[] vertices; BasicMaterialContent bmc; // todo: not yet sure which format versions of Blender between 2.62 and 2.65 use. if (float.Parse(file.VersionNumber) >= 2.66f) // uses edges, loops, and polys (Blender 2.66+) { vertices = loadNewModel(file, mesh, verts, normals, out bmc); } else // uses MFace (Blender 2.49-2.61) { vertices = loadOldModel(file, mesh, verts, normals, out bmc); } MeshBuilder mb = MeshBuilder.StartMesh(Name); foreach (VertexPositionNormalTexture v in vertices) { mb.CreatePosition(v.Position); } int uvChannel = mb.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(0)); int normalChannel = mb.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); int j = 0; foreach (VertexPositionNormalTexture v in vertices) { mb.SetVertexChannelData(uvChannel, v.TextureCoordinate); mb.SetVertexChannelData(normalChannel, v.Normal); mb.AddTriangleVertex(j++); } }
private MeshContent ExtractMesh(aiMesh aiMesh) { if (!String.IsNullOrEmpty(aiMesh.mName.Data)) { log("modelname " + aiMesh.mName.Data); meshBuilder = MeshBuilder.StartMesh(aiMesh.mName.Data); } else { meshBuilder = MeshBuilder.StartMesh(Path.GetFileNameWithoutExtension(filename)); } if (!aiMesh.HasPositions()) { throw new Exception("MOdel does not have Position"); } // Add additional vertex channels for texture coordinates and normals if (aiMesh.HasTextureCoords(0)) { textureCoordinateDataIndex = meshBuilder.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(0)); } else if (aiMesh.HasVertexColors(0)) { colorCoordinateDataIndex = meshBuilder.CreateVertexChannel <Vector4>(VertexChannelNames.Color(0)); } if (aiMesh.HasNormals()) { normalDataIndex = meshBuilder.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); } if (aiMesh.HasTangentsAndBitangents()) { tangentDataIndex = meshBuilder.CreateVertexChannel <Vector3>(VertexChannelNames.Tangent(0)); binormalDataIndex = meshBuilder.CreateVertexChannel <Vector3>(VertexChannelNames.Binormal(0)); } if (aiMesh.HasBones()) { boneDataIndex = meshBuilder.CreateVertexChannel <BoneWeightCollection>(VertexChannelNames.Weights(0)); } var numFaces = (int)aiMesh.mNumFaces; var numVertices = (int)aiMesh.mNumVertices; var aiPositions = aiMesh.mVertices; var aiNormals = aiMesh.mNormals; var aiTextureCoordsAll = aiMesh.mTextureCoords; var aiTextureCoords = (aiTextureCoordsAll != null) ? aiTextureCoordsAll[0] : null; for (int j = 0; j < aiMesh.mNumVertices; j++) { meshBuilder.CreatePosition(aiMesh.mVertices[j].x, aiMesh.mVertices[j].y, aiMesh.mVertices[j].z); } meshBuilder.SetMaterial(GetMaterial(aiMesh)); var aiFaces = aiMesh.mFaces; var dxIndices = new uint[numFaces * 3]; for (int k = 0; k < numFaces; ++k) { var aiFace = aiFaces[k]; var aiIndices = aiFace.mIndices; for (int j = 0; j < 3; ++j) { int index = (int)aiIndices[j]; if (aiMesh.HasTextureCoords(0)) { meshBuilder.SetVertexChannelData(textureCoordinateDataIndex, new Vector2(aiMesh.mTextureCoords[0][index].x, aiMesh.mTextureCoords[0][index].y)); } else if (aiMesh.HasVertexColors(0)) { meshBuilder.SetVertexChannelData(colorCoordinateDataIndex, new Vector4(aiMesh.mColors[0][index].r, aiMesh.mColors[0][index].g, aiMesh.mColors[0][index].b, aiMesh.mColors[0][index].a)); } if (aiMesh.HasNormals()) { meshBuilder.SetVertexChannelData(normalDataIndex, new Vector3(aiMesh.mNormals[index].x, aiMesh.mNormals[index].y, aiMesh.mNormals[index].z)); } if (aiMesh.HasTangentsAndBitangents()) { meshBuilder.SetVertexChannelData(tangentDataIndex, new Vector3(aiMesh.mTangents[index].x, aiMesh.mTangents[index].y, aiMesh.mTangents[index].z)); meshBuilder.SetVertexChannelData(binormalDataIndex, new Vector3(aiMesh.mBitangents[index].x, aiMesh.mBitangents[index].y, aiMesh.mBitangents[index].z)); } if (aiMesh.HasBones()) { BoneWeightCollection BoneWeightCollection = new BoneWeightCollection(); if (wbone.ContainsKey(index)) { foreach (var item in wbone[index]) { BoneWeightCollection.Add(new BoneWeight(item.Key, item.Value)); } } meshBuilder.SetVertexChannelData(boneDataIndex, BoneWeightCollection); } meshBuilder.AddTriangleVertex(index); } } MeshContent meshContent = meshBuilder.FinishMesh(); return(meshContent); }
public override NodeContent Import(string filename, ContentImporterContext context) { context.Logger.LogMessage("Importing H3D file: {0}", filename); _identity = new ContentIdentity(filename, GetType().Name); _rootNode = new NodeContent() { Identity = _identity, Name = "RootNode" }; var scene = FormatIdentifier.IdentifyAndOpen(filename); var model = scene.Models[0]; if (!scene.Textures.Any()) { var path = Path.Combine(Path.GetDirectoryName(filename), $"{Path.GetFileNameWithoutExtension(filename)}@Textures{Path.GetExtension(filename)}"); if (File.Exists(path)) { context.Logger.LogMessage($"Found texture file {path}. Loading data..."); scene.Merge(FormatIdentifier.IdentifyAndOpen(path, model.Skeleton)); } else { context.Logger.LogMessage($"Couldn't find texture file {path}!"); } } // Textures var textures = new Dictionary <string, Texture2DContent>(); foreach (var texture in scene.Textures) { var bitmapContent = new PixelBitmapContent <Color>(texture.Width, texture.Height) { Identity = _identity, Name = texture.Name }; bitmapContent.SetPixelData(texture.ToRGBA()); var textureContent = new Texture2DContent() { Identity = _identity, Name = texture.Name }; textureContent.Faces[0].Add(bitmapContent); textures.Add(textureContent.Name, textureContent); } // Materials var materials = new Dictionary <string, H3DMaterialContent>(); foreach (var material in model.Materials) { #if DEBUG var hlslCode = new HLSLShaderGenerator(material.MaterialParams) { BoneCount = model.Skeleton.Count }.GetShader(); var glslCode = new GLSLFragmentShaderGenerator(material.MaterialParams).GetFragShader(); #endif var materialContent = new H3DMaterialContent() { Identity = _identity, Name = material.Name, Effect = new EffectContent { Identity = _identity, Name = "H3DEffect", EffectCode = new HLSLShaderGenerator(material.MaterialParams) { BoneCount = model.Skeleton.Count }.GetShader() }, Material = material.Name, FaceCulling = (H3DFaceCulling?)material.MaterialParams.FaceCulling, EmissionColor = material.MaterialParams.EmissionColor.ToXNA(), AmbientColor = material.MaterialParams.AmbientColor.ToXNA(), DiffuseColor = material.MaterialParams.DiffuseColor.ToXNA(), Specular0Color = material.MaterialParams.Specular0Color.ToXNA(), Specular1Color = material.MaterialParams.Specular1Color.ToXNA(), Constant0Color = material.MaterialParams.Constant0Color.ToXNA(), Constant1Color = material.MaterialParams.Constant1Color.ToXNA(), Constant2Color = material.MaterialParams.Constant2Color.ToXNA(), Constant3Color = material.MaterialParams.Constant3Color.ToXNA(), Constant4Color = material.MaterialParams.Constant4Color.ToXNA(), Constant5Color = material.MaterialParams.Constant5Color.ToXNA(), BlendColor = material.MaterialParams.BlendColor.ToXNA(), DepthBufferRead = material.MaterialParams.DepthBufferRead, DepthBufferWrite = material.MaterialParams.DepthBufferWrite, StencilBufferRead = material.MaterialParams.StencilBufferRead, StencilBufferWrite = material.MaterialParams.StencilBufferWrite, }; var texCount = 0; if (material.EnabledTextures[0]) { texCount++; } if (material.EnabledTextures[1]) { texCount++; } if (material.EnabledTextures[2]) { texCount++; } materialContent.TextureList = new Texture2DContent[texCount]; if (material.EnabledTextures[0]) { materialContent.TextureList[0] = textures[material.Texture0Name]; } if (material.EnabledTextures[1]) { materialContent.TextureList[1] = textures[material.Texture1Name]; } if (material.EnabledTextures[2]) { materialContent.TextureList[2] = textures[material.Texture2Name]; } materialContent.TextureSamplerSettings = material.TextureMappers.Select(tm => new TextureSamplerSettings() { WrapU = tm.WrapU.ToXNAWrap(), WrapV = tm.WrapV.ToXNAWrap(), MagFilter = (TextureSamplerSettings.TextureMagFilter)tm.MagFilter, MinFilter = (TextureSamplerSettings.TextureMinFilter)tm.MinFilter }).ToArray(); materials.Add(material.Name, materialContent); } // Geometry var meshes = new List <MeshContent>(); for (var i = 0; i < model.Meshes.Count; i++) { var modelMesh = model.Meshes[i]; if (modelMesh.Type == H3DMeshType.Silhouette) { continue; } var mesh = new MeshContent() { Identity = _identity, Name = $"{model.Materials[modelMesh.MaterialIndex].Name}_node{i}", }; var geometry = new GeometryContent { Identity = _identity, Material = materials[model.Materials[modelMesh.MaterialIndex].Name] }; var vertices = GetWorldSpaceVertices(model.Skeleton, modelMesh); var baseVertex = mesh.Positions.Count; foreach (var vertex in vertices) { mesh.Positions.Add(vertex.Position.ToVector3()); } geometry.Vertices.AddRange(Enumerable.Range(baseVertex, vertices.Length)); foreach (var attribute in modelMesh.Attributes) { if (attribute.Name >= PICAAttributeName.BoneIndex) { continue; } switch (attribute.Name) { case PICAAttributeName.Position: break; // Already added case PICAAttributeName.Normal: geometry.Vertices.Channels.Add(VertexChannelNames.Normal(0), vertices.Select(vertex => vertex.Normal.ToVector3())); break; case PICAAttributeName.Tangent: geometry.Vertices.Channels.Add(VertexChannelNames.Tangent(0), vertices.Select(vertex => vertex.Tangent.ToVector3())); break; case PICAAttributeName.Color: geometry.Vertices.Channels.Add(VertexChannelNames.Color(0), vertices.Select(vertex => vertex.Color.ToColor())); break; case PICAAttributeName.TexCoord0: geometry.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(0), vertices.Select(vertex => vertex.TexCoord0.ToVector2().ToUV())); break; case PICAAttributeName.TexCoord1: geometry.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(1), vertices.Select(vertex => vertex.TexCoord1.ToVector2().ToUV())); break; case PICAAttributeName.TexCoord2: geometry.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(2), vertices.Select(vertex => vertex.TexCoord2.ToVector2().ToUV())); break; } } var vertexOffset = 0; var xnaWeights = new List <BoneWeightCollection>(); foreach (var modelSubMesh in modelMesh.SubMeshes) { geometry.Indices.AddRange(modelSubMesh.Indices.Select(index => (int)index)); var vertexCount = modelSubMesh.MaxIndex + 1 - vertexOffset; var subMeshVertices = vertices.Skip(vertexOffset).Take(vertexCount).ToList(); if (modelSubMesh.Skinning == H3DSubMeshSkinning.Smooth) { foreach (var vertex in subMeshVertices) { var list = new BoneWeightCollection(); for (var index = 0; index < 4; index++) { var bIndex = vertex.Indices[index]; var weight = vertex.Weights[index]; if (weight == 0) { break; } if (bIndex < modelSubMesh.BoneIndicesCount && bIndex > -1) { bIndex = modelSubMesh.BoneIndices[bIndex]; } else { bIndex = 0; } list.Add(new BoneWeight(model.Skeleton[bIndex].Name, weight)); } xnaWeights.Add(list); } } else { foreach (var vertex in vertices) { var bIndex = vertex.Indices[0]; if (bIndex < modelSubMesh.BoneIndices.Length && bIndex > -1) { bIndex = modelSubMesh.BoneIndices[bIndex]; } else { bIndex = 0; } xnaWeights.Add(new BoneWeightCollection() { new BoneWeight(model.Skeleton[bIndex].Name, 0) }); } } vertexOffset += vertexCount; } geometry.Vertices.Channels.Add(VertexChannelNames.Weights(0), xnaWeights); mesh.Geometry.Add(geometry); meshes.Add(mesh); } foreach (var mesh in meshes) { _rootNode.Children.Add(mesh); } var rootBone = ImportBones(model); _rootNode.Children.Add(rootBone); if (!scene.SkeletalAnimations.Any()) { var path = Path.Combine(Path.GetDirectoryName(filename), $"{Path.GetFileNameWithoutExtension(filename)}@Animations{Path.GetExtension(filename)}"); if (File.Exists(path)) { context.Logger.LogMessage($"Found animation file {path}. Loading data..."); scene.Merge(FormatIdentifier.IdentifyAndOpen(path, model.Skeleton)); } else { context.Logger.LogMessage($"Couldn't find animation file {path}!"); } } foreach (var animation in ImportSkeletalAnimations(scene)) { rootBone.Animations.Add(animation.Name, animation); } foreach (var animation in ImportMaterialAnimations(scene)) { _rootNode.Children.Add(animation); } return(_rootNode); }
public override NodeContent Import(string filename, ContentImporterContext context) { ContentIdentity identity = new ContentIdentity(filename, GetType().Name); const int MAX_BONE_WEIGHTS = 4; VertexBoneWeightLimitConfig boneConfig = new VertexBoneWeightLimitConfig(MAX_BONE_WEIGHTS); AssimpImporter importer = new AssimpImporter(); importer.SetConfig(boneConfig); importer.AttachLogStream(new LogStream((msg, userData) => context.Logger.LogMessage(msg))); Scene scene = importer.ImportFile(filename, PostProcessSteps.FlipUVs | PostProcessSteps.JoinIdenticalVertices | PostProcessSteps.Triangulate | PostProcessSteps.SortByPrimitiveType | PostProcessSteps.FindInvalidData | PostProcessSteps.LimitBoneWeights | PostProcessSteps.FixInFacingNormals); // Root node NodeContent rootNode = new NodeContent { Name = scene.RootNode.Name, Identity = identity, Transform = Matrix.Transpose(ToXna(scene.RootNode.Transform)) }; // Materials MaterialContent[] materials = new MaterialContent[scene.MaterialCount]; for (int m = 0; m < scene.MaterialCount; m++) { materials[m] = new BasicMaterialContent(); materials[m].Identity = identity; // For some reason, there is all kinds of nasty junk in this string: materials[m].Name = CleanInput(scene.Materials[m].Name); for (int t = 0; t < scene.Materials[m].GetTextureCount(TextureType.Diffuse); t++) { TextureSlot diffuseMap = scene.Materials[m].GetTexture(TextureType.Diffuse, t); if (!String.IsNullOrEmpty(diffuseMap.FilePath)) { materials[m].Textures.Add("Texture" + (t > 0 ? t.ToString() : ""), new ExternalReference <TextureContent>(diffuseMap.FilePath, identity)); } } } // Bones // We find 'mesh container' nodes with the best names for those meshes while looking for the bones, // and will need them later when we create the MeshContents. I have a feeling that this won't work // in general, and may need to be made more robust. Dictionary <Mesh, string> meshNames = new Dictionary <Mesh, string>(); Dictionary <Node, BoneContent> nodeToBoneMap = new Dictionary <Node, BoneContent>(); BoneContent skeleton = null; // The root bone for the model. List <Node> hierarchyNodes = scene.RootNode.Children.SelectDeep(n => n.Children).ToList(); foreach (Node node in hierarchyNodes) { BoneContent bone = new BoneContent { Name = node.Name, Transform = Matrix.Transpose(ToXna(node.Transform)) }; if (node.MeshIndices != null) { // This node is a 'mesh container' instead of a bone, so we only care about extracting the name of the mesh. foreach (int meshIndex in node.MeshIndices) { if (!meshNames.ContainsKey(scene.Meshes[meshIndex])) { meshNames.Add(scene.Meshes[meshIndex], node.Name); } } } else if (node.Parent == scene.RootNode) { if (skeleton == null) { // This will be our skeleton so put the animations here: if (scene.HasAnimations) { foreach (Animation assimpAnim in scene.Animations) { if (assimpAnim.HasNodeAnimations) { AnimationContent newAnim = new AnimationContent(); newAnim.Identity = identity; newAnim.Duration = TimeSpan.FromSeconds(assimpAnim.DurationInTicks / assimpAnim.TicksPerSecond); newAnim.Name = assimpAnim.Name; foreach (NodeAnimationChannel nac in assimpAnim.NodeAnimationChannels) { Node animatedNode = hierarchyNodes.Find(n => n.Name == nac.NodeName); AnimationChannel newChan = BuildAnimtionChannel(animatedNode, nac); newAnim.Channels.Add(nac.NodeName, newChan); } if (String.IsNullOrEmpty(assimpAnim.Name)) { bone.Animations.Add("SkelematorNoAnimationName", newAnim); } else { bone.Animations.Add(assimpAnim.Name, newAnim); } } } } rootNode.Children.Add(bone); skeleton = bone; } else { context.Logger.LogWarning(null, identity, "Found multiple skeletons in the model, throwing extras away..."); } } else { BoneContent parent = nodeToBoneMap[node.Parent]; parent.Children.Add(bone); } nodeToBoneMap.Add(node, bone); } // Meshes Dictionary <Mesh, MeshContent> meshes = new Dictionary <Mesh, MeshContent>(); foreach (Mesh sceneMesh in scene.Meshes) { // See comment about meshNames at the beginning of the bone section. MeshBuilder mb = MeshBuilder.StartMesh(meshNames[sceneMesh]); mb.SwapWindingOrder = true; // Appears to require this... int positionIndex = -1; for (int v = 0; v < sceneMesh.VertexCount; v++) { Vector3D vert = sceneMesh.Vertices[v]; // CreatePosition should just return a 0-based index of the newly added vertex. positionIndex = mb.CreatePosition(new Vector3(vert.X, vert.Y, vert.Z)); if (positionIndex != v) { throw new InvalidContentException("Something unexpected happened while building a MeshContent from the Assimp scene mesh's vertices. The scene mesh may contains duplicate vertices."); } } if (positionIndex + 1 < 3) { throw new InvalidContentException("There were not enough vertices in the Assimp scene mesh."); } // Create vertex channels int normalVertexChannelIndex = mb.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); int[] texCoordVertexChannelIndex = new int[sceneMesh.TextureCoordsChannelCount]; for (int x = 0; x < sceneMesh.TextureCoordsChannelCount; x++) { texCoordVertexChannelIndex[x] = mb.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(x)); } int boneWeightVertexChannelIndex = -1; if (sceneMesh.HasBones) { boneWeightVertexChannelIndex = mb.CreateVertexChannel <BoneWeightCollection>(VertexChannelNames.Weights()); } // Prepare vertex channel data BoneWeightCollection[] boneWeightData = null; if (sceneMesh.HasBones) { boneWeightData = new BoneWeightCollection[sceneMesh.VertexCount]; for (int v = 0; v < sceneMesh.VertexCount; v++) { boneWeightData[v] = new BoneWeightCollection(); } foreach (Bone sceneMeshBone in sceneMesh.Bones) { // We have to assume that the bone's name matches up with a node, and therefore one of our BoneContents. foreach (VertexWeight sceneMeshBoneWeight in sceneMeshBone.VertexWeights) { boneWeightData[sceneMeshBoneWeight.VertexID].Add(new BoneWeight(sceneMeshBone.Name, sceneMeshBoneWeight.Weight)); } } for (int v = 0; v < sceneMesh.VertexCount; v++) { if (boneWeightData[v].Count <= 0) { throw new InvalidContentException("Encountered vertices without bone weights."); } boneWeightData[v].NormalizeWeights(); } } // Set the per-geometry data mb.SetMaterial(materials[sceneMesh.MaterialIndex]); mb.SetOpaqueData(new OpaqueDataDictionary()); // Add each vertex for (int f = 0; f < sceneMesh.FaceCount; f++) { if (sceneMesh.Faces[f].IndexCount != 3) { throw new InvalidContentException("Only triangular faces allowed."); } for (int t = 0; t < 3; t++) { mb.SetVertexChannelData(normalVertexChannelIndex, ToXna(sceneMesh.Normals[sceneMesh.Faces[f].Indices[t]])); for (int x = 0; x < sceneMesh.TextureCoordsChannelCount; x++) { mb.SetVertexChannelData(texCoordVertexChannelIndex[x], ToXnaVector2((sceneMesh.GetTextureCoords(x))[sceneMesh.Faces[f].Indices[t]])); } if (sceneMesh.HasBones) { mb.SetVertexChannelData(boneWeightVertexChannelIndex, boneWeightData[sceneMesh.Faces[f].Indices[t]]); } mb.AddTriangleVertex((int)(sceneMesh.Faces[f].Indices[t])); } } MeshContent mesh = mb.FinishMesh(); rootNode.Children.Add(mesh); meshes.Add(sceneMesh, mesh); } return(rootNode); }
/// <summary> /// The importer's entry point. /// Called by the framework when importing a game asset. /// </summary> /// <param name="filename">Name of a game asset file.</param> /// <param name="context"> /// Contains information for importing a game asset, such as a logger interface. /// </param> /// <returns>Resulting game asset.</returns> public override NodeContent Import(string filename, ContentImporterContext context) { NodeContent rootNode = new NodeContent { Identity = new ContentIdentity(filename), Name = Path.GetFileNameWithoutExtension(filename) }; try { // Import file using Meshellator. Scene scene = MeshellatorLoader.ImportFromFile(filename); // Create materials. //System.Diagnostics.Debugger.Launch(); Dictionary <Material, MaterialContent> materials = GetMaterials(scene); // Convert Meshellator scene to XNA mesh. foreach (Mesh mesh in scene.Meshes) { MeshContent meshContent = new MeshContent { Name = mesh.Name }; foreach (Point3D position in mesh.Positions) { meshContent.Positions.Add(ConvertPoint3D(position)); } MaterialContent material = (mesh.Material != null) ? materials[mesh.Material] : new BasicMaterialContent { DiffuseColor = new Vector3(0.5f), VertexColorEnabled = false }; GeometryContent geometryContent = new GeometryContent { Material = material }; meshContent.Geometry.Add(geometryContent); geometryContent.Indices.AddRange(mesh.Indices); for (int i = 0; i < mesh.Positions.Count; ++i) { geometryContent.Vertices.Add(i); } List <Vector2> textureCoordinates = new List <Vector2>(); for (int i = 0; i < mesh.Positions.Count; ++i) { Vector2 textureCoordinate = (i < mesh.TextureCoordinates.Count) ? ConvertTextureCoordinate(mesh.TextureCoordinates[i]) : Vector2.Zero; textureCoordinates.Add(textureCoordinate); } geometryContent.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(0), textureCoordinates); List <Vector3> normals = new List <Vector3>(); foreach (Vector3D normal in mesh.Normals) { normals.Add(ConvertVector3D(normal)); } geometryContent.Vertices.Channels.Add(VertexChannelNames.Normal(), normals); // Finish the mesh and set the transform. if (SwapWindingOrder) { MeshHelper.SwapWindingOrder(meshContent); } meshContent.Transform = ConvertTransform(mesh.Transform); // Add the mesh to the model rootNode.Children.Add(meshContent); } return(rootNode); } catch (InvalidContentException) { // InvalidContentExceptions do not need further processing throw; } catch (Exception e) { // Wrap exception with content identity (includes line number) throw new InvalidContentException( "Unable to parse file. Exception:\n" + e.ToString(), rootNode.Identity, e); } }
private void ValidateMorphTarget(MeshContent morphTarget) { // Check whether morph target is the child of the base mesh. var baseMesh = morphTarget.Parent as MeshContent; if (baseMesh == null) { string message = String.Format( CultureInfo.InvariantCulture, "Morph target \"{0}\" needs to be a child of (linked to) the base mesh.", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } // Check whether number of positions matches. if (baseMesh.Positions.Count != morphTarget.Positions.Count) { string message = String.Format( CultureInfo.InvariantCulture, "Number of positions in morph target \"{0}\" does not match base mesh. (Base mesh: {1}, morph target: {2})", morphTarget.Name, baseMesh.Positions.Count, morphTarget.Positions.Count); throw new InvalidContentException(message, morphTarget.Identity); } // Check whether number of submeshes matches. int numberOfSubmeshes = baseMesh.Geometry.Count; if (numberOfSubmeshes != morphTarget.Geometry.Count) { string message = String.Format( CultureInfo.InvariantCulture, "Number of submeshes in morph target \"{0}\" does not match base mesh. (Base mesh: {1}, morph target: {2})", morphTarget.Name, numberOfSubmeshes, morphTarget.Geometry.Count); throw new InvalidContentException(message, morphTarget.Identity); } for (int i = 0; i < numberOfSubmeshes; i++) { var baseGeometry = baseMesh.Geometry[i]; var morphGeometry = morphTarget.Geometry[i]; // Check whether number of vertices matches. if (baseGeometry.Vertices.VertexCount != morphGeometry.Vertices.VertexCount) { string message = String.Format( CultureInfo.InvariantCulture, "Number vertices in morph target \"{0}\" does not match base mesh.", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } // Check for "Normal" channel. if (!baseGeometry.Vertices.Channels.Contains(VertexChannelNames.Normal())) { string message = String.Format( CultureInfo.InvariantCulture, "Normal vectors missing in base mesh \"{0}\".", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } if (!morphGeometry.Vertices.Channels.Contains(VertexChannelNames.Normal())) { string message = String.Format( CultureInfo.InvariantCulture, "Normal vectors missing in morph target \"{0}\".", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } int baseNormalsCount = baseGeometry.Vertices.Channels[VertexChannelNames.Normal()].Count; int morphNormalsCount = morphGeometry.Vertices.Channels[VertexChannelNames.Normal()].Count; if (baseNormalsCount != morphNormalsCount) { string message = String.Format( CultureInfo.InvariantCulture, "Number of vertices in morph target \"{0}\" (submesh: {1}) does not match base mesh. (Base mesh: {2}, morph target: {3})", morphTarget.Name, i, baseNormalsCount, morphNormalsCount); throw new InvalidContentException(message, morphTarget.Identity); } // Check whether number of indices matches. if (baseGeometry.Indices.Count != morphGeometry.Indices.Count) { string message = String.Format( CultureInfo.InvariantCulture, "Indices in morph target \"{0}\" do not match base mesh.", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } } // Check whether morph target has children. if (morphTarget.Children.Count > 0) { _context.Logger.LogWarning( null, _input.Identity, "The children of the morph target \"{0}\" will be ignored.", morphTarget.Name); } }
} // 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
/// <summary> /// Import a VOX file as Model /// </summary> private NodeContent VoxelProcess(VoxelContent voxel, ContentImporterContext context) { XYZI[] voxels = voxel.Voxels; uint[] palette = voxel.Palette; var scale = voxel.RealSize / voxel.GridSize; Vector3 centerOffset = new Vector3(1f, 0f, 1f) * (voxel.RealSize / -2f); var corner000 = new Point3(0, 0, 0); var corner100 = new Point3(1, 0, 0); var corner010 = new Point3(0, 1, 0); var corner110 = new Point3(1, 1, 0); var corner001 = new Point3(0, 0, 1); var corner101 = new Point3(1, 0, 1); var corner011 = new Point3(0, 1, 1); var corner111 = new Point3(1, 1, 1); var Forward = Vector3.Forward; var Backward = Vector3.Backward; var Left = Vector3.Left; var Right = Vector3.Right; var Up = Vector3.Up; var Down = Vector3.Down; for (int i = 0; i < voxels.Length; i++) { var pt000 = voxels[i].Point.Add(ref corner000); var pt100 = voxels[i].Point.Add(ref corner100); var pt010 = voxels[i].Point.Add(ref corner010); var pt110 = voxels[i].Point.Add(ref corner110); var pt001 = voxels[i].Point.Add(ref corner001); var pt101 = voxels[i].Point.Add(ref corner101); var pt011 = voxels[i].Point.Add(ref corner011); var pt111 = voxels[i].Point.Add(ref corner111); // back var p0 = pt000.ToVector3(); var p1 = pt100.ToVector3(); var p2 = pt010.ToVector3(); var p3 = pt110.ToVector3(); // front var p4 = pt001.ToVector3(); var p5 = pt101.ToVector3(); var p6 = pt011.ToVector3(); var p7 = pt111.ToVector3(); Vector3.Multiply(ref p0, ref scale, out p0); Vector3.Add(ref p0, ref centerOffset, out p0); Vector3.Multiply(ref p1, ref scale, out p1); Vector3.Add(ref p1, ref centerOffset, out p1); Vector3.Multiply(ref p2, ref scale, out p2); Vector3.Add(ref p2, ref centerOffset, out p2); Vector3.Multiply(ref p3, ref scale, out p3); Vector3.Add(ref p3, ref centerOffset, out p3); Vector3.Multiply(ref p4, ref scale, out p4); Vector3.Add(ref p4, ref centerOffset, out p4); Vector3.Multiply(ref p5, ref scale, out p5); Vector3.Add(ref p5, ref centerOffset, out p5); Vector3.Multiply(ref p6, ref scale, out p6); Vector3.Add(ref p6, ref centerOffset, out p6); Vector3.Multiply(ref p7, ref scale, out p7); Vector3.Add(ref p7, ref centerOffset, out p7); vertex.Color.PackedValue = palette[voxels[i].ColorIndex]; if ((voxels[i].SharedSides & Sides.Forward) == 0) { vertex.Normal = Forward; AddVertex(ref p1); AddVertex(ref p3); AddVertex(ref p0); AddVertex(ref p0); AddVertex(ref p3); AddVertex(ref p2); } if ((voxels[i].SharedSides & Sides.Backward) == 0) { vertex.Normal = Backward; AddVertex(ref p4); AddVertex(ref p6); AddVertex(ref p5); AddVertex(ref p5); AddVertex(ref p6); AddVertex(ref p7); } if ((voxels[i].SharedSides & Sides.Left) == 0) { vertex.Normal = Left; AddVertex(ref p2); AddVertex(ref p6); AddVertex(ref p0); AddVertex(ref p0); AddVertex(ref p6); AddVertex(ref p4); } if ((voxels[i].SharedSides & Sides.Right) == 0) { vertex.Normal = Right; AddVertex(ref p1); AddVertex(ref p5); AddVertex(ref p3); AddVertex(ref p3); AddVertex(ref p5); AddVertex(ref p7); } if ((voxels[i].SharedSides & Sides.Up) == 0) { vertex.Normal = Up; AddVertex(ref p7); AddVertex(ref p6); AddVertex(ref p3); AddVertex(ref p3); AddVertex(ref p6); AddVertex(ref p2); } if ((voxels[i].SharedSides & Sides.Down) == 0) { vertex.Normal = Down; AddVertex(ref p5); AddVertex(ref p1); AddVertex(ref p4); AddVertex(ref p4); AddVertex(ref p1); AddVertex(ref p0); } } MeshContent mesh = new MeshContent(); mesh.Name = "voxel"; for (int pi = 0; pi < this.vertices.Count; pi++) { mesh.Positions.Add(this.vertices[pi].Position); } var geom = new GeometryContent(); mesh.Geometry.Add(geom); BasicMaterialContent material = new BasicMaterialContent(); geom.Material = material; for (int pi = 0; pi < this.vertices.Count; pi++) { geom.Vertices.Add(pi); } for (int ii = 0; ii < this.indices.Count; ii++) { geom.Indices.Add(this.indices[ii]); } List <Vector3> normals = new List <Vector3>(); List <Color> colors = new List <Color>(); for (int vi = 0; vi < this.vertices.Count; vi++) { var vertex = vertices[vi]; normals.Add(vertex.Normal); colors.Add(vertex.Color); } geom.Vertices.Channels.Add <Vector3>(VertexChannelNames.Normal(0), normals); geom.Vertices.Channels.Add <Color>(VertexChannelNames.Color(0), colors); return(mesh); }
private static void MakeRelativeMorphTargets(MeshContent baseMesh, List <MeshContent> morphTargets) { foreach (var morphTarget in morphTargets) { // Make positions relative to base mesh. // (Positions are stored in MeshContent.Positions.) var basePositions = baseMesh.Positions; var morphPositions = morphTarget.Positions; int numberOfPositions = basePositions.Count; for (int i = 0; i < numberOfPositions; i++) { morphPositions[i] -= basePositions[i]; } // Make normals relative to base mesh. // (Normals are stored as a vertex channel per submesh.) int numberOfSubmeshes = baseMesh.Geometry.Count; for (int i = 0; i < numberOfSubmeshes; i++) { var baseGeometry = baseMesh.Geometry[i]; var morphGeometry = morphTarget.Geometry[i]; var baseNormals = baseGeometry.Vertices.Channels.Get <Vector3>(VertexChannelNames.Normal()); var morphNormals = morphGeometry.Vertices.Channels.Get <Vector3>(VertexChannelNames.Normal()); int numberOfNormals = baseNormals.Count; for (int j = 0; j < numberOfNormals; j++) { morphNormals[j] -= baseNormals[j]; } } } }
private MeshContent CreateMesh(Mesh sceneMesh) { var mesh = new MeshContent { Name = sceneMesh.Name }; // Position vertices are shared at the mesh level foreach (var vert in sceneMesh.Vertices) { mesh.Positions.Add(new Vector3(vert.X, vert.Y, vert.Z)); } var geom = new GeometryContent { Material = _materials[sceneMesh.MaterialIndex] }; // Geometry vertices reference 1:1 with the MeshContent parent, // no indirection is necessary. //geom.Vertices.Positions.AddRange(mesh.Positions); geom.Vertices.AddRange(Enumerable.Range(0, sceneMesh.VertexCount)); geom.Indices.AddRange(sceneMesh.GetIndices()); if (sceneMesh.HasBones) { var xnaWeights = new List <BoneWeightCollection>(); for (var i = 0; i < geom.Indices.Count; i++) { var list = new BoneWeightCollection(); for (var boneIndex = 0; boneIndex < sceneMesh.BoneCount; boneIndex++) { var bone = sceneMesh.Bones[boneIndex]; foreach (var weight in bone.VertexWeights) { if (weight.VertexID != i) { continue; } list.Add(new BoneWeight(bone.Name, weight.Weight)); } } if (list.Count > 0) { xnaWeights.Add(list); } } geom.Vertices.Channels.Add(VertexChannelNames.Weights(0), xnaWeights); } // Individual channels go here if (sceneMesh.HasNormals) { geom.Vertices.Channels.Add(VertexChannelNames.Normal(), ToXna(sceneMesh.Normals)); } for (var i = 0; i < sceneMesh.TextureCoordinateChannelCount; i++) { geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(i), ToXnaTexCoord(sceneMesh.TextureCoordinateChannels[i])); } mesh.Geometry.Add(geom); return(mesh); }
} // 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
public override NodeContent Import(string filename, ContentImporterContext context) { var identity = new ContentIdentity(filename, GetType().Name); var importer = new AssimpImporter(); importer.AttachLogStream(new LogStream((msg, userData) => context.Logger.LogMessage(msg))); var scene = importer.ImportFile(filename, PostProcessSteps.FlipUVs | // So far appears necessary PostProcessSteps.JoinIdenticalVertices | PostProcessSteps.Triangulate | PostProcessSteps.SortByPrimitiveType | PostProcessSteps.FindInvalidData ); var rootNode = new NodeContent { Name = scene.RootNode.Name, Identity = identity, Transform = ToXna(scene.RootNode.Transform) }; // TODO: Materials var materials = new List <MaterialContent>(); foreach (var sceneMaterial in scene.Materials) { var diffuse = sceneMaterial.GetTexture(TextureType.Diffuse, 0); materials.Add(new BasicMaterialContent() { Name = sceneMaterial.Name, Identity = identity, Texture = new ExternalReference <TextureContent>(diffuse.FilePath, identity) }); } // Meshes var meshes = new Dictionary <Mesh, MeshContent>(); foreach (var sceneMesh in scene.Meshes) { if (!sceneMesh.HasVertices) { continue; } var mesh = new MeshContent { Name = sceneMesh.Name }; // Position vertices are shared at the mesh level foreach (var vert in sceneMesh.Vertices) { mesh.Positions.Add(new Vector3(vert.X, vert.Y, vert.Z)); } var geom = new GeometryContent { Name = string.Empty, //Material = materials[sceneMesh.MaterialIndex] }; // Geometry vertices reference 1:1 with the MeshContent parent, // no indirection is necessary. geom.Vertices.Positions.AddRange(mesh.Positions); geom.Vertices.AddRange(Enumerable.Range(0, sceneMesh.VertexCount)); geom.Indices.AddRange(sceneMesh.GetIntIndices()); // Individual channels go here if (sceneMesh.HasNormals) { geom.Vertices.Channels.Add(VertexChannelNames.Normal(), ToXna(sceneMesh.Normals)); } for (var i = 0; i < sceneMesh.TextureCoordsChannelCount; i++) { geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(i), ToXnaVector2(sceneMesh.GetTextureCoords(i))); } mesh.Geometry.Add(geom); rootNode.Children.Add(mesh); meshes.Add(sceneMesh, mesh); } // Bones var bones = new Dictionary <Node, BoneContent>(); var hierarchyNodes = scene.RootNode.Children.SelectDeep(n => n.Children).ToList(); foreach (var node in hierarchyNodes) { var bone = new BoneContent { Name = node.Name, Transform = Matrix.Transpose(ToXna(node.Transform)) }; if (node.Parent == scene.RootNode) { rootNode.Children.Add(bone); } else { var parent = bones[node.Parent]; parent.Children.Add(bone); } // Copy the bone's name to the MeshContent - this appears to be // the way it comes out of XNA's FBXImporter. foreach (var meshIndex in node.MeshIndices) { meshes[scene.Meshes[meshIndex]].Name = node.Name; } bones.Add(node, bone); } return(rootNode); }
/// <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); }