/// <summary> /// fix the material ids /// sometimes inside a node more material ids are set on faces /// than materials are actually used /// </summary> /// <param name="nodeData"></param> private static async Task CheckMatIds(sNodeData nodeData) { await Task.Run(() => { IMesh mesh = nodeData.ViewNode.Mesh; // get the used material from the node int iNumMaterials = 0; IMtl nodeMaterial = nodeData.ViewNode.Node.Mtl; // first check if a material is on the node // check as well if its a multimaterial if (nodeMaterial != null) { iNumMaterials = nodeMaterial.NumSubMtls > 0 ? nodeMaterial.NumSubMtls : 1; } // cycle through all face and check the MatID IFace[] arrFaces = mesh.Faces.ToArray(); for (int i = 0; i < arrFaces.Length; i++) { if (arrFaces[i].MatID > iNumMaterials) { nodeData.ViewNode.WrongMatIDCount++; } } }); }
/// <summary> /// check for degenerated faces /// those faces can break up all connected face normals /// when a vertex normal is created, the degenerated faces will result in a NaN normal. /// this will kill all connected normals for faces that are using the same vertices as well /// </summary> /// <param name="nodeData"></param> /// <returns></returns> private static async Task CheckDegTris(sNodeData nodeData) { await Task.Run(() => { // cycle through all faces for (int i = 0; i < nodeData.Faces.Count; i++) { // check if the face area of the current face is smaller than float epsilon // this is the fastest and easiest test to check for degenerated faces if (nodeData.Faces[i].GetFaceArea(nodeData.Verts) > float.Epsilon) { continue; } nodeData.ViewNode.ContainsDegTriErrors = true; nodeData.ViewNode.HashDegTris.Add(i); } }); }
/// <summary> /// check for isolated map verts /// the max sdk property IsoVerts inside IMeshMap is broken so do it by hand /// </summary> /// <param name="nodeData"></param> /// <returns></returns> private static async Task CheckIsoUVVerts(sNodeData nodeData) { await Task.Run(() => { IMesh mesh = nodeData.ViewNode.Mesh; HashSet <int> vertIndices; IList <ITVFace> tvFaces; // cycle through all vertex channels // -2 vertex illumination // -1 vertx alpha // 0 vertex color // NumUVChannels is the upper limit for (int i = -2; i <= nodeData.NumUVChannels; i++) { if (!mesh.MapSupport(i)) { continue; } // create a hashset with all vertice indices vertIndices = mesh.MapVerts(i).Count.CreateHashSet(); // get the tv faces from the specific mesh map channel tvFaces = mesh.MapFaces(i); // cycle through all the tv faces for (int j = 0; j < tvFaces.Count; j++) { // get all the tvvertices from the current face and remove it from the hashset vertIndices.ExceptWith(tvFaces[j].GetAllVerts()); } // if there are still vertices inside the hashset we found our not used isolated vertices if (vertIndices.Count > 0) { nodeData.ViewNode.IsoUVVertCount += vertIndices.Count; } } }); }
/// <summary> /// check for degenerated uv faces /// this can happen when a quad is triangulated the wrong way on the mesh /// image an a quad where one triangle pointing up but its mapped planar top /// the uv triangles for this face will produce a degenerated triangle depending on how the /// edge of the mesh quad is created. turning the edge can solve this problem /// degenerated uv triangles will break up normal presentation as well. /// if you are using mikktspace those uv faces will produce NaN tangents and binormals /// </summary> /// <param name="nodeData"></param> /// <returns></returns> private static async Task CheckDegUVTris(sNodeData nodeData) { await Task.Run(() => { IMesh mesh = nodeData.ViewNode.Mesh; IList <IPoint3> tvVerts; IList <ITVFace> tvFaces; // cycle through real UVChannels where NumUVChannels is the upper limit for (int i = 1; i <= nodeData.NumUVChannels; i++) { // check if this is supported if (!mesh.MapSupport(i)) { continue; } // get the tv verts and faces tvVerts = mesh.MapVerts(i); tvFaces = mesh.MapFaces(i); // cycle through all tv faces for (int j = 0; j < tvFaces.Count; j++) { // check if the face area is smaller than float epsilon if (tvFaces[j].GetFaceArea(tvVerts) > float.Epsilon) { continue; } nodeData.ViewNode.ContainsDegUvErrors = true; nodeData.ViewNode.HashDegUVTris.Add(j); } } }); }
/// <summary> /// analyze a nodeview if it contains errors /// </summary> /// <param name="nodeView"></param> /// <returns></returns> internal static async Task Analyze(NodeView nodeView) { if (!nodeView.IsValidWorkingNode) { return; } sNodeData nodeData = new sNodeData(); await Task.Run(() => nodeData.SetData(nodeView)); if (!nodeData.IsValid) { return; } await Task.WhenAll ( NodeAnalyzer.CheckMisc(nodeData), NodeAnalyzer.CheckDegTris(nodeData), NodeAnalyzer.CheckBadSMGTris(nodeData), NodeAnalyzer.CheckDegUVTris(nodeData) ); }
/// <summary> /// check for bad smoothing groups where a vertex normla would result in a NaN normal /// this is bad for normal map presentation /// </summary> /// <param name="nodeData"></param> /// <returns></returns> private static async Task CheckBadSMGTris(sNodeData nodeData) { await Task.Run(() => { int[] faceIndices; Dictionary <uint, IPoint3> mapNormals; IPoint3 vecNormal; IPoint3[] sumNormals; HashSet <int> hashFaceIndices; HashSet <int> hashVertFaces; IFace face; // cycle trough all verts for (uint i = 0; i < nodeData.Verts.Count; i++) { // if the vert is not found inside our map we found an isolated vertex if (!nodeData.VertUsingFaces.TryGetValue(i, out hashVertFaces)) { nodeData.ViewNode.IsoVertCount++; continue; } faceIndices = hashVertFaces.ToArray(); // create a new map for vertex normals per smoothing group mapNormals = new Dictionary <uint, IPoint3>(faceIndices.Length); // create a hash for faces which result in NaN vert normals hashFaceIndices = new HashSet <int>(); // cycle through all face from the current vertex for (int j = 0; j < faceIndices.Length; j++) { // get the current face face = nodeData.Faces[faceIndices[j]]; // check if a vertex normal for a smoothing group is available // create a new vertex normal if not if (!mapNormals.TryGetValue(face.SmGroup, out vecNormal)) { vecNormal = AssemblyFunctions.GlobalInterface.Point3.Create(0, 0, 0); } // add the face normal to vertex normal inside our vertex normal map per smoothing group // don't normalize, that way the face area which is double of the normal length will weight the result mapNormals[face.SmGroup] = vecNormal.AddPoint3(face.GetFaceNormal(nodeData.Verts)); // remember the current face index hashFaceIndices.Add(faceIndices[j]); } sumNormals = mapNormals.Values.ToArray(); // cycle through all vertx normals per smoothing group for (int j = 0; j < sumNormals.Length; j++) { // normalize and check for NaN if (sumNormals[j].Normalize().ValidVector()) { continue; } nodeData.ViewNode.HashBadSMGTris.UnionWith(hashFaceIndices); nodeData.ViewNode.ContainsBadSMGErrors = true; } } }); }
private static async Task CheckMisc(sNodeData nodeData) { await Task.WhenAll(NodeAnalyzer.CheckIsoUVVerts(nodeData), NodeAnalyzer.CheckMatIds(nodeData)); }