/// <summary> /// Generate the tangents and binormals (tangent frames) for each vertex in the mesh. /// </summary> /// <param name="mesh">The mesh which will have add tangent and binormal channels added.</param> /// <param name="textureCoordinateChannelName">The Vector2 texture coordinate channel used to generate tangent frames.</param> /// <param name="tangentChannelName"></param> /// <param name="binormalChannelName"></param> public static void CalculateTangentFrames(MeshContent mesh, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName) { foreach (var geom in mesh.Geometry) { CalculateTangentFrames(geom, textureCoordinateChannelName, tangentChannelName, binormalChannelName); } }
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; }
/// <summary> /// Generates vertex normals by accumulation of triangle face normals. /// </summary> /// <param name="mesh">The mesh which will recieve the normals.</param> /// <param name="overwriteExistingNormals">Overwrite or skip over geometry with existing normals.</param> /// <remarks> /// This calls <see cref="CalculateNormals(GeometryContent, bool)"/> to do the work. /// </remarks> public static void CalculateNormals(MeshContent mesh, bool overwriteExistingNormals) { foreach (var geom in mesh.Geometry) { CalculateNormals(geom, overwriteExistingNormals); } }
/// <summary> /// Merge any positions in the <see cref="PositionCollection"/> of the /// specified mesh that are at a distance less than the specified tolerance /// from each other. /// </summary> /// <param name="mesh">Mesh to be processed.</param> /// <param name="tolerance">Tolerance value that determines how close /// positions must be to each other to be merged.</param> /// <remarks> /// This method will also update the <see cref="VertexContent.PositionIndices"/> /// in the <see cref="GeometryContent"/> of the specified mesh. /// </remarks> public static void MergeDuplicatePositions(MeshContent mesh, float tolerance) { if (mesh == null) { throw new ArgumentNullException("mesh"); } // TODO Improve performance with spatial partitioning scheme var indexLists = new List <IndexUpdateList>(); foreach (var geom in mesh.Geometry) { var list = new IndexUpdateList(geom.Vertices.PositionIndices); indexLists.Add(list); } for (var i = mesh.Positions.Count - 1; i >= 1; i--) { var pi = mesh.Positions[i]; for (var j = i - 1; j >= 0; j--) { var pj = mesh.Positions[j]; if (Vector3.Distance(pi, pj) <= tolerance) { foreach (var list in indexLists) { list.Update(i, j); } mesh.Positions.RemoveAt(i); } } } }
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; } }
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]]; } }
private static bool MeshHasSkinning(MeshContent mesh) { bool flag; bool flag1; IEnumerator<GeometryContent> enumerator = mesh.Geometry.GetEnumerator(); try { do { flag1 = enumerator.MoveNext(); if (flag1) { GeometryContent current = enumerator.Current; flag1 = current.Vertices.Channels.Contains(VertexChannelNames.Weights()); } else { flag = true; return flag; } } while (flag1); flag = false; return flag; } finally { flag1 = enumerator == null; if (!flag1) { enumerator.Dispose(); } } flag = true; return flag; }
public static void MergeDuplicateVertices(MeshContent mesh) { foreach (var geom in mesh.Geometry) { MergeDuplicateVertices(geom); } }
internal ModelMeshContent(string name, MeshContent sourceMesh, ModelBoneContent parentBone, BoundingSphere boundingSphere, IList<ModelMeshPartContent> meshParts) { _name = name; _sourceMesh = sourceMesh; _parentBone = parentBone; _boundingSphere = boundingSphere; _meshParts = new ModelMeshPartContentCollection(meshParts); }
private MeshBuilder(string name) { _meshContent = new MeshContent(); _vertexChannels = new List <VertexChannel>(); _vertexChannelData = new List <object>(); _currentGeometryContent = new GeometryContent(); _currentOpaqueData = new OpaqueDataDictionary(); _geometryDirty = true; Name = name; }
/// <summary> /// Merge vertices with the same <see cref="VertexContent.PositionIndices"/> and /// <see cref="VertexChannel"/> data within the <see cref="MeshContent.Geometry"/> /// of this mesh. If you want to merge positions too, call /// <see cref="MergeDuplicatePositions"/> on your mesh before this function. /// </summary> /// <param name="mesh">Mesh to be processed</param> public static void MergeDuplicateVertices(MeshContent mesh) { if (mesh == null) { throw new ArgumentNullException("mesh"); } foreach (var geom in mesh.Geometry) { MergeDuplicateVertices(geom); } }
/// <summary> /// Checks whether a mesh contains skinning information. /// </summary> /// <returns>Returns true if mesh has skin</returns> public static bool MeshHasSkinning(MeshContent mesh) { foreach (GeometryContent geometry in mesh.Geometry) { if (!geometry.Vertices.Channels.Contains(VertexChannelNames.Weights())) { return false; } } return true; }
private static void AddVertexReorderChannel(MeshContent mesh) { foreach (var geometry in mesh.Geometry) { // Add the original vertex indices 0, 1, 2, ... n as a new vertex channel. When // the vertices are optimized the vertex channel contains the vertex reorder map. // The index needs to be stored as Byte4 because Int32 is not allowed in a // VertexChannelCollection. var vertexOrder = Enumerable.Range(0, geometry.Vertices.VertexCount) .Select(index => new Byte4 { PackedValue = (uint)index }); geometry.Vertices.Channels.Add("VertexReorder", vertexOrder); } }
private static void UpdatePositionIndices(MeshContent mesh, int from, int to) { foreach (var geom in mesh.Geometry) { for (var i = 0; i < geom.Vertices.PositionIndices.Count; i++) { var index = geom.Vertices.PositionIndices[i]; if (index == from) { geom.Vertices.PositionIndices[i] = to; } } } }
private static void AddMorphTarget(DRSceneNodeContent sceneNode, MeshContent morphTarget) { var meshNode = sceneNode as DRMeshNodeContent; if (meshNode == null) { string message = String.Format( CultureInfo.InvariantCulture, "Morph target \"{0}\" needs to be a child of the base mesh.", morphTarget.Name); throw new InvalidContentException(message, morphTarget.Identity); } if (meshNode.InputMorphTargets == null) meshNode.InputMorphTargets = new List<MeshContent>(); meshNode.InputMorphTargets.Add(morphTarget); }
//-------------------------------------------------------------- private void ProcessVertexChannels(MeshContent mesh) { foreach (GeometryContent geometry in mesh.Geometry) { var channels = geometry.Vertices.Channels; foreach (var channel in channels.ToArray()) { // Get current index. (ProcessVertexChannel could have modified the vertex // channel collection!) int channelIndex = channels.IndexOf(channel); if (channelIndex < 0) continue; ProcessVertexChannel(geometry, channelIndex); } } }
// Gets vertex reorder maps for geometry, removes "VertexReorder" channel. private static int[][] GetVertexReorderMaps(MeshContent mesh) { int numberOfSubmeshes = mesh.Geometry.Count; int[][] vertexReorderMaps = new int[numberOfSubmeshes][]; for (int i = 0; i < numberOfSubmeshes; i++) { var vertices = mesh.Geometry[i].Vertices; var vertexReorderChannel = vertices.Channels.Get<Byte4>("VertexReorder"); vertexReorderMaps[i] = new int[vertexReorderChannel.Count]; for (int j = 0; j < vertexReorderMaps[i].Length; j++) vertexReorderMaps[i][j] = (int)vertexReorderChannel[j].PackedValue; vertices.Channels.Remove(vertexReorderChannel); } return vertexReorderMaps; }
/// <summary> /// Reverses the triangle winding order of the mesh. /// </summary> /// <param name="mesh">The mesh which will be modified.</param> /// <remarks> /// This method is useful when changing the direction of backface culling /// like when switching between left/right handed coordinate systems. /// </remarks> public static void SwapWindingOrder(MeshContent mesh) { // Gotta have a mesh to run! if (mesh == null) { throw new ArgumentNullException("mesh"); } foreach (var geom in mesh.Geometry) { for (var i = 0; i < geom.Indices.Count; i += 3) { var first = geom.Indices[i]; var last = geom.Indices[i + 2]; geom.Indices[i] = last; geom.Indices[i + 2] = first; } } }
private void ProcessMesh(MeshContent mesh) { if (MeshSplitter.NeedsSplitting(mesh, maxBones)) { modelModified = true; MeshSplitter splitter = new MeshSplitter(mesh, maxBones); List<MeshContent> meshes = splitter.Split(); foreach (MeshContent m in meshes) { MeshHelper.MergeDuplicatePositions(m, 0); MeshHelper.MergeDuplicateVertices(m); MeshHelper.OptimizeForCache(m); } MeshContent firstMesh = meshes[0]; NodeContent parent = mesh.Parent; List<NodeContent> children = new List<NodeContent>(); foreach (NodeContent child in mesh.Children) children.Add(child); foreach (NodeContent child in children) { mesh.Children.Remove(child); } parent.Children.Remove(mesh); foreach (MeshContent m in meshes) { parent.Children.Add(m); } foreach (NodeContent child in children) { firstMesh.Children.Add(child); } foreach (NodeContent child in firstMesh.Children) { ProcessNode(child); } } }
private static void MergeDuplicatePositions(MeshContent mesh, float tolerance) { Debug.Assert(mesh != null); Debug.Assert(tolerance > 0); var positions = mesh.Positions.Select(p => (Vector3F)p).ToList(); int[] positionRemap; int numberOfDuplicates = GeometryHelper.MergeDuplicatePositions(positions, tolerance, out positionRemap); if (numberOfDuplicates > 0) { mesh.Positions.Clear(); for (int i = 0; i < positions.Count; i++) mesh.Positions[i] = (Vector3)positions[i]; foreach (var geometry in mesh.Geometry) { var positionIndices = geometry.Vertices.PositionIndices; int numberOfVertices = geometry.Vertices.VertexCount; for (int i = 0; i < numberOfVertices; i++) positionIndices[i] = positionRemap[positionIndices[i]]; } } }
/// <summary> /// Generate the tangents and binormals (tangent frames) for each vertex in the mesh. /// </summary> /// <param name="mesh">The mesh which will have add tangent and binormal channels added.</param> /// <param name="textureCoordinateChannelName">The Vector2 texture coordinate channel used to generate tangent frames.</param> /// <param name="tangentChannelName"></param> /// <param name="binormalChannelName"></param> public static void CalculateTangentFrames(MeshContent mesh, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName) { foreach (var geom in mesh.Geometry) CalculateTangentFrames(geom, textureCoordinateChannelName, tangentChannelName, binormalChannelName); }
/*static*/ private void OptimizeForCache(MeshContent mesh) { Debug.Assert(mesh != null); foreach (var geometry in mesh.Geometry) { var vertices = geometry.Vertices; int numberOfVertices = vertices.VertexCount; var positions = vertices.Positions.Select(p => (Vector3F)p).ToArray(); var indices = geometry.Indices.ToList(); int[] vertexRemap; int[] duplicateVertices; OptimizeForCache(positions, indices, out vertexRemap, out duplicateVertices, mesh.Identity); // ----- Recreate vertices and indices. // Pseuso-code (see DirectXMesh.FinalizeVB): // // for each j in nVerts // newIndex = vertexRemap[j] // if (newIndex != -1) // memcpy(newVB + newIndex * stride, // oldVB + j * stride, // stride) // // for each j in nDupVerts // newIndex = vertexRemap[j + nVerts] // if (newIndex != -1) // memcpy(newVB + newIndex * stride, // oldVB + dup[j] * stride, // stride) // Copy original vertices. int[] positionIndices = vertices.PositionIndices.ToArray(); var channels = vertices.Channels.ToArray(); vertices.Channels.Clear(); vertices.RemoveRange(0, vertices.VertexCount); // Reserve vertex entries. for (int i = 0; i < vertexRemap.Length; i++) { if (vertexRemap[i] != -1) vertices.Add(0); } // Add reordered vertices. for (int oldIndex = 0; oldIndex < numberOfVertices; oldIndex++) { int newIndex = vertexRemap[oldIndex]; if (newIndex != -1) vertices.PositionIndices[newIndex] = positionIndices[oldIndex]; } Debug.Assert(vertexRemap.Length == numberOfVertices + duplicateVertices.Length); // Add duplicate vertices. for (int i = 0; i < duplicateVertices.Length; i++) { int newIndex = vertexRemap[numberOfVertices + i]; if (newIndex != -1) vertices.PositionIndices[newIndex] = positionIndices[duplicateVertices[i]]; } // Add vertex channels. foreach (var oldChannel in channels) { var newChannel = vertices.Channels.Add(oldChannel.Name, oldChannel.ElementType, null); // Add reordered vertices. for (int oldIndex = 0; oldIndex < numberOfVertices; oldIndex++) { int newIndex = vertexRemap[oldIndex]; if (newIndex != -1) newChannel[newIndex] = oldChannel[oldIndex]; } // Add duplicate vertices. for (int i = 0; i < duplicateVertices.Length; i++) { int newIndex = vertexRemap[numberOfVertices + i]; if (newIndex != -1) newChannel[newIndex] = oldChannel[duplicateVertices[i]]; } } // Add new indices. geometry.Indices.Clear(); geometry.Indices.AddRange(indices); } }
/// <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); }
public static bool NeedsSplitting(MeshContent mesh, int maxBones) { SortedDictionary<string, object> skinnedBones = new SortedDictionary<string, object>(); foreach (GeometryContent geom in mesh.Geometry) { VertexChannel<BoneWeightCollection> weightChannel = null; foreach (VertexChannel channel in geom.Vertices.Channels) { if (channel.Name == VertexChannelNames.Weights()) { weightChannel = (VertexChannel<BoneWeightCollection>)channel; break; } } if (weightChannel != null) { foreach (BoneWeightCollection weights in weightChannel) { foreach (BoneWeight weight in weights) { if (!skinnedBones.ContainsKey(weight.BoneName)) skinnedBones.Add(weight.BoneName, null); } } } } return skinnedBones.Keys.Count > maxBones; }
private MeshBuilder(string name) { Name = name; _meshContent = new MeshContent(); }
public static void OptimizeForCache(MeshContent mesh) { throw new NotImplementedException(); }
public static void SwapWindingOrder(MeshContent mesh) { throw new NotImplementedException(); }
public static void CalculateTangentFrames(MeshContent mesh, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName) { throw new NotImplementedException(); }
public static void CalculateNormals(MeshContent mesh, bool overwriteExistingNormals) { throw new NotImplementedException(); }
public static void MergeDuplicatePositions(MeshContent mesh, float tolerance) { throw new NotImplementedException(); }
public static void OptimizeForCache(MeshContent mesh) { // We don't throw here as non-optimized still works. }
public static void MergeDuplicateVertices(MeshContent mesh) { 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="mesh">The mesh which will recieve the normals.</param> /// <param name="overwriteExistingNormals">Overwrite or skip over geometry with existing normals.</param> /// <remarks> /// This calls <see cref="CalculateNormals(GeometryContent, bool)"/> to do the work. /// </remarks> public static void CalculateNormals(MeshContent mesh, bool overwriteExistingNormals) { foreach (var geom in mesh.Geometry) CalculateNormals(geom, overwriteExistingNormals); }
public static void MergeDuplicateVertices(MeshContent mesh) { foreach (var geom in mesh.Geometry) MergeDuplicateVertices(geom); }
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; }
/// <summary> /// Reverses the triangle winding order of the mesh. /// </summary> /// <param name="mesh">The mesh which will be modified.</param> /// <remarks> /// This method is useful when changing the direction of backface culling /// like when switching between left/right handed coordinate systems. /// </remarks> public static void SwapWindingOrder(MeshContent mesh) { // Gotta have a mesh to run! if (mesh == null) throw new ArgumentNullException("mesh"); foreach (var geom in mesh.Geometry) { for (var i = 0; i < geom.Indices.Count; i += 3) { var first = geom.Indices[i]; var last = geom.Indices[i+2]; geom.Indices[i] = last; geom.Indices[i+2] = first; } } }
public MeshSplitter(MeshContent content, int maxBones) { this.mesh = content; this.maxBones = maxBones; }