override public void Calculate(ref KrablMesh.MeshEdges mesh, KMProcessorProgram parentProgram = null) { base.Calculate(ref mesh); SubdivideQ sub = new SubdivideQ(); SubdivideQParameters par = new SubdivideQParameters(); sub.progressDelegate = delegate(string text, float val) { ReportProgress(text, val); }; par.trisToQuads = trisToQuads; par.trisToQuadsMaxAngle = trisToQuadsMaxEdgeAngle; par.iterations = iterations; par.recalculateNormals = (normalMode == NormalMode.Recalculate); if (platformID != null) { for (int i = 0; i < iterationsOverridePlatform.Length; ++i) { if (platformID.Equals(iterationsOverridePlatform[i])) { par.iterations = iterationsOverride[i]; break; } } } sub.Execute(ref mesh, par); }
override public void Calculate(ref KrablMesh.MeshEdges mesh, KMProcessorProgram parentProgram = null) { base.Calculate(ref mesh); bool recalcNormals = false; KrablMesh.CreaseDetect.ClearAllCreases(mesh); if (creasesFromNormals) { KrablMesh.CreaseDetect.MarkCreasesFromFaceNormals(mesh, 1.0f); // if (creaseStrength < 1.0f) recalcNormals = true; } else { recalcNormals = true; } if (creasesFromEdgeAngles) { KrablMesh.CreaseDetect.MarkCreasesFromEdgeAngles(mesh, minEdgeAngle, 1.0f); recalcNormals = true; } if (creasesFromMaterialSeams) { KrablMesh.CreaseDetect.MarkCreasesFromMaterialSeams(mesh, 1.0f); recalcNormals = true; } if (recalcNormals) { mesh.CalculateFaceVertexNormalsFromEdgeCreases(); } }
/// <summary> /// Searches through all edges of the mesh and compares the vertex normals on each face connected /// to an edge. If there is a difference in the vertex normals, the edge gets marked as crease. /// This method is automatically executed every time a Unity mesh is converted to a Krabl Mesh Library mesh. /// </summary> /// <param name='mesh'> /// The mesh to search. /// </param> /// <param name='creaseStrength'> /// The crease value to set the edges to. Currently only 1.0f has any effect. /// </param> public static void MarkCreasesFromFaceNormals(MeshEdges mesh, float creaseStrength = 1.0f) { mesh.CalculateEdgeLinkedFaces(); int count = mesh.edgeCount(); for (int edgeIndex = 0; edgeIndex < count; ++edgeIndex) { Edge edge = mesh.edges[edgeIndex]; int v1 = edge.v[0]; int v2 = edge.v[1]; if (edge.linkedFaces.Count == 2) { Face fa = mesh.faces[edge.linkedFaces[0]]; Face fb = mesh.faces[edge.linkedFaces[1]]; Vector3 n1a = fa.VertexNormalForVertexIndex(v1); Vector3 n2a = fa.VertexNormalForVertexIndex(v2); Vector3 n1b = fb.VertexNormalForVertexIndex(v1); Vector3 n2b = fb.VertexNormalForVertexIndex(v2); if (KrablMesh.UnityUtils.Vector3CompareWithTolerance(n1a, n1b, mesh.equalityTolerance) != 0 || KrablMesh.UnityUtils.Vector3CompareWithTolerance(n2a, n2b, mesh.equalityTolerance) != 0) { edge.crease = creaseStrength; } } } }
public static void TriangulateWithEdges(MeshEdges mesh) { if (mesh.topology == MeshTopology.Triangles) { return; } int oldNumFaces = mesh.faceCount(); Triangulate((BaseMesh)mesh); int newNumFaces = mesh.faceCount(); // Iterate through the new faces for (int faceIndex = oldNumFaces; faceIndex < newNumFaces; ++faceIndex) { Face f = mesh.faces[faceIndex]; Edge e = new Edge(f.v[0], f.v[1]); // the new edge is always 0->1 because triangulate is constructed to do that. mesh.AddEdge(e); } mesh.RegenerateVertexLinkedEdges(); mesh.GenerateEdgeTopology(); //mesh.GenerateEdgeList(); // not good! Looses edge information! // What was really needed is looking for the new edges created.. leave the old ones alone! //mesh.CalculateEdgeLinkedFaces(); }
override public void Calculate(ref KrablMesh.MeshEdges mesh, KMProcessorProgram parentProgram = null) { base.Calculate(ref mesh); if (materials == MaterialOption.Merge) { mesh.numMaterials = 1; int numFaces = mesh.faceCount(); for (int i = 0; i < numFaces; ++i) { mesh.faces[i].material = 0; } } if (vertexColors == Option.Remove) { mesh.hasVertexColors = false; } if (uv1Option == Option.Remove) { mesh.hasUV1 = false; } if (uv2Option == Option.Remove) { mesh.hasUV2 = false; } /*if (skin == Option.Remove) { * mesh.hasBoneWeights = false; * mesh.bindposes = null; * }*/ }
/// <summary> /// Sets the crease value of all edges in the mesh to 0. /// </summary> /// <param name='mesh'> /// The mesh to process. /// </param> public static void ClearAllCreases(MeshEdges mesh) { int numEdges = mesh.edgeCount(); for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex) { mesh.edges[edgeIndex].crease = 0.0f; } }
public void Cleanup() { _parameters = null; progressDelegate = null; pdePerVertex = null; heapNodes = null; heap = null; mesh = null; }
public UnityEngine.Mesh Process(UnityEngine.Mesh umesh, string platformID = null) { if (bypass == false) { KrablMesh.MeshEdges kmesh = ProcessToKrablMesh(umesh, platformID); UnityEngine.Mesh result = new UnityEngine.Mesh(); KrablMesh.ImportExport.MeshEdgesToUnityMesh(kmesh, result); return(result); } return(umesh); }
/// <summary> /// Change the normals of a mesh to produce a flat-shaded look. This will increase the vertex count as all vertices need to be split. /// </summary> /// <param name='unityMesh'> /// The mesh to flat shade. /// </param> public static void FlatShadeMesh(Mesh unityMesh) { KrablMesh.MeshEdges kmesh = new KrablMesh.MeshEdges(); KrablMesh.ImportExport.UnityMeshToMeshEdges(unityMesh, kmesh); KrablMesh.CreaseDetect.MarkCreasesFromEdgeAngles(kmesh, 0.0f); kmesh.CalculateFaceVertexNormalsFromEdgeCreases(); KrablMesh.ImportExport.MeshEdgesToUnityMesh(kmesh, unityMesh); }
/// <summary> /// Change to normals of a mesh to produce a smooth-shaded look. This will reduce the vertex count and all mesh corners will just have one normal. /// </summary> /// <param name='unityMesh'> /// The mesh to smooth shade. /// </param> public static void SmoothShadeMesh(Mesh unityMesh) { KrablMesh.MeshEdges kmesh = new KrablMesh.MeshEdges(); KrablMesh.ImportExport.UnityMeshToMeshEdges(unityMesh, kmesh); KrablMesh.CreaseDetect.ClearAllCreases(kmesh); kmesh.CalculateFaceVertexNormalsFromEdgeCreases(); KrablMesh.ImportExport.MeshEdgesToUnityMesh(kmesh, unityMesh); }
/// <summary> /// Searches the mesh for edges that are connected to faces of different materials (submeshes). These are /// marked as creases. /// </summary> /// <param name='mesh'> /// The mesh to search. /// </param> /// <param name='creaseStrength'> /// The crease value to set the edges to. Currently only 1.0f has any effect. /// </param> public static void MarkCreasesFromMaterialSeams(MeshEdges mesh, float creaseStrength = 1.0f) { int numEdges = mesh.edgeCount(); for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex) { if (mesh.isEdgeMaterialSeam(edgeIndex)) { mesh.edges[edgeIndex].crease = creaseStrength; } } }
/// <summary> /// Searches through all edges of the mesh and calculates the angle between the faces connected to the edge. /// The the angle is above the threshold, the edge is marked as a crease. /// </summary> /// <param name='mesh'> /// The mesh to search. /// </param> /// <param name='angleThreshold'> /// All edges with angles equal or larger than this are marked as creases. /// </param> /// <param name='creaseStrength'> /// The crease value to set the edges to. Currently only 1.0f has any effect. /// </param> public static void MarkCreasesFromEdgeAngles(MeshEdges mesh, float angleThreshold, float creaseStrength = 1.0f) { mesh.CalculateFaceNormals(); int numEdges = mesh.edgeCount(); for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex) { if (mesh.CalculateEdgeAngle(edgeIndex) >= angleThreshold) { mesh.edges[edgeIndex].crease = creaseStrength; } } }
/// <summary> /// Subdivides a mesh by applying a quads-based subdivision algorithm. Optionally, triangles are first merged to quads if possible to produce a better topology. /// For every iteration the face count of the mesh quadruples. The result mesh has quad topology. /// </summary> /// <param name='unityMesh'> /// The mesh to subdivide. /// </param> /// <param name='iterations'> /// The number of iterations the algorithm should perform. High numbers (>3) quickly lead to producing more triangles than /// unity can handle! (>65k). /// </param> /// <param name='trisToQuads'> /// Attempt to convert triangles to quads before the subdivision (highly recommended). /// </param> public static void SubdivideQuadsMesh(Mesh unityMesh, int iterations, bool trisToQuads = true) { KrablMesh.MeshEdges kmesh = new KrablMesh.MeshEdges(); KrablMesh.SubdivideQ sub = new KrablMesh.SubdivideQ(); KrablMesh.SubdivideQParameters subpars = new KrablMesh.SubdivideQParameters(); KrablMesh.ImportExport.UnityMeshToMeshEdges(unityMesh, kmesh); subpars.trisToQuads = trisToQuads; subpars.iterations = iterations; sub.Execute(ref kmesh, subpars); KrablMesh.ImportExport.MeshEdgesToUnityMesh(kmesh, unityMesh); }
// Simple implementation based on linkedEdges. It would be possible without edges, but this algo needs edges anyways bool _isVertexBorder(MeshEdges mesh, int vertIndex) { List <int> linkedEdges = mesh.linkedEdgesForVert(vertIndex); for (int i = 0; i < linkedEdges.Count; ++i) { int edgeIndex = linkedEdges[i]; if (mesh.IsEdgeValid(edgeIndex) && mesh.IsEdgeBorder(edgeIndex)) { return(true); } } return(false); }
// the mesh needs to have its edge list and creases calculated public void InitializeCollapsing(MeshEdges _mesh, SimplifyParameters parameters) { mesh = _mesh; _parameters = parameters; // Gain some time by avoiding unecessary calculations if (_mesh.hasBoneWeights == false) { parameters.boneWeightProtection = 0.0f; } if (_mesh.hasVertexColors == false) { parameters.vertexColorProtection = 0.0f; } _calculatePdePerVertex(); // construct heap int numEdges = mesh.edgeCount(); heapNodes = new HeapNode <CollapseInfo> [numEdges]; heap = new MinHeap <CollapseInfo>(); int progressCounter = kProgressGroups; float t = Time.realtimeSinceStartup; for (int i = 0; i < numEdges; ++i) { CollapseInfo pc = new CollapseInfo(); _calculateEdgeCost(i, pc); heapNodes[i] = heap.Insert(new HeapNode <CollapseInfo>(pc.cost, pc)); progressCounter--; if (progressCounter <= 0) { progressCounter = kProgressGroups; if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null) { t = Time.realtimeSinceStartup; progressDelegate("Initialize Edge " + i + "/" + numEdges, 0.1f * ((float)i) / ((float)numEdges)); } } } // shortcut for fastest calc noPenalties = (parameters.checkTopology == false && parameters.maxEdgesPerVertex == 0 && parameters.preventNonManifoldEdges == false && parameters.boneWeightProtection <= 0.0f && parameters.vertexColorProtection <= 0.0f); //sharpnessLimitSqr = parameters.minTriangleShape*parameters.minTriangleShape; //if (parameters.minTriangleShape < 0.0f) sharpnessLimitSqr = 0.0f; }
/// <summary> /// Perform quad-based mesh subdivision /// </summary> /// <param name='mesh'> /// A mesh (KrablMesh.MeshEdges) to subdivide. The mesh will be replaced by a new mesh structure. /// </param> /// <param name='parameters'> /// The parameters to use during the subdivison. /// </param> public void Execute(ref MeshEdges mesh, SubdivideQParameters parameters) { float progSteps = (float)parameters.iterations; if (parameters.trisToQuads) { progSteps += 1.0f; } if (parameters.recalculateNormals) { progSteps += 1.0f; } float progStep = (progSteps > 0.0f) ? (1.0f / progSteps) : 1.0f; float progress = 0.0f; smooth = parameters.smooth; recalculateNormals = parameters.recalculateNormals; if (parameters.trisToQuads) { if (progressDelegate != null) { progressDelegate("TrisToQuads", progress); progress += progStep; } Ops.TrisToQuads(mesh, parameters.trisToQuadsMaxAngle); } for (int i = 0; i < parameters.iterations; ++i) { if (progressDelegate != null) { progressDelegate("Iteration " + (i + 1), progress); progress += progStep; } Subdivide(ref mesh); } if (recalculateNormals) { if (progressDelegate != null) { progressDelegate("Recalculate Normals", progress); progress += progStep; } mesh.CalculateFaceVertexNormalsFromEdgeCreases(); } }
override public void Calculate(ref KrablMesh.MeshEdges mesh, KMProcessorProgram parentProgram = null) { base.Calculate(ref mesh); KrablMesh.Simplify sim = new KrablMesh.Simplify(); KrablMesh.SimplifyParameters par = new KrablMesh.SimplifyParameters(); sim.progressDelegate = delegate(string text, float val) { ReportProgress(text, val); }; // par.maximumError = maximumError; par.targetFaceCount = targetTriangleCount; if (platformID != null) { for (int i = 0; i < ttcOverridePlatform.Length; ++i) { if (platformID.Equals(ttcOverridePlatform[i])) { par.targetFaceCount = ttcOverrideTargetTriangleCount[i]; break; } } } par.recalculateVertexPositions = allowVertexReposition; par.preventNonManifoldEdges = preventNonManifoldEdges; par.borderWeight = borders; par.creaseWeight = creases; par.uvSeamWeight = uvSeams; par.uv2SeamWeight = uv2Seams; par.materialSeamWeight = materialSeams; // par.minTriangleShape = minTriangleShape; par.boneWeightProtection = boneWeightProtection; par.vertexColorProtection = vertexColorProtection; // par.vertexNormalProtection = vertexNormalProtection; sim.Execute(ref mesh, par); }
/// <summary> /// Simplify a mesh by collapsing edges until the target face count is reached. /// </summary> /// <param name='unityMesh'> /// The mesh to simplify. /// </param> /// <param name='targetFaceCount'> /// The number of triangles to reduce the mesh to. If this is higher than the initial /// number of triangles, no processing will occur. /// </param> /// <param name='highQuality'> /// Use slower, but more precise calculations. /// </param> public static void SimplifyMesh(Mesh unityMesh, int targetFaceCount, bool highQuality = true) { KrablMesh.MeshEdges kmesh = new KrablMesh.MeshEdges(); KrablMesh.Simplify sim = new KrablMesh.Simplify(); KrablMesh.SimplifyParameters simpars = new KrablMesh.SimplifyParameters(); KrablMesh.ImportExport.UnityMeshToMeshEdges(unityMesh, kmesh); simpars.targetFaceCount = targetFaceCount; simpars.recalculateVertexPositions = highQuality; simpars.checkTopology = !highQuality; simpars.maxEdgesPerVertex = highQuality ? 18 : 0; if (highQuality == false) { simpars.preventNonManifoldEdges = false; simpars.boneWeightProtection = 0.0f; simpars.vertexColorProtection = 0.0f; } sim.Execute(ref kmesh, simpars); KrablMesh.ImportExport.MeshEdgesToUnityMesh(kmesh, unityMesh); }
public KrablMesh.MeshEdges ProcessToKrablMesh(UnityEngine.Mesh umesh, string platformID = null) { KrablMesh.MeshEdges kmesh = new KrablMesh.MeshEdges(); inMeshDescription = MeshDescription(umesh); KrablMesh.ImportExport.UnityMeshToMeshEdges(umesh, kmesh, inputTolerance); if (bypass == false) { foreach (KrablMesh.Processor p in processors) { if (p.enabled == true) { p.SetBuildPlatform(platformID); //float t = Time.realtimeSinceStartup; p.Calculate(ref kmesh, this); // Debug.Log("Processor " + p.Name() + " took " + (Time.realtimeSinceStartup - t)*1000.0f + " ms."); } } } return(kmesh); }
/// <summary> /// Perform mesh simplification. /// </summary> /// <param name='mesh'> /// A mesh (KrablMesh.MeshEdges) to simplify. The mesh will be changed and might even be replaced by a new mesh structure. /// </param> /// <param name='parameters'> /// The parameters to use during the simplification. /// </param> public void Execute(ref MeshEdges mesh, SimplifyParameters parameters) { if (mesh.vertCount() == 0) { return; } if (mesh.topology != MeshTopology.Triangles) { Ops.TriangulateWithEdges(mesh); } if (progressDelegate != null) { progressDelegate("Initialize", 0.0f); } InitializeCollapsing(mesh, parameters); if (parameters.edgesToCollapse > 0) { Collapse(parameters.edgesToCollapse); } else if (parameters.targetFaceCount > 0) { int totalFacesToRemove = mesh.numValidFaces - parameters.targetFaceCount; int progressCounter = 0; float t = Time.realtimeSinceStartup + kProgressInterval; while (mesh.numValidFaces > parameters.targetFaceCount) { Collapse(1); progressCounter--; if (progressCounter <= 0) { progressCounter = kProgressGroups; if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null) { t = Time.realtimeSinceStartup; int facesRemoved = totalFacesToRemove - (mesh.numValidFaces - parameters.targetFaceCount); progressDelegate("Mesh Faces " + mesh.numValidFaces + "->" + parameters.targetFaceCount, 0.1f + 0.9f * ((float)facesRemoved) / ((float)totalFacesToRemove)); } } } } else if (parameters.maximumError > 0) { // Determine size of mesh to scale errors meshSize = _determineMeshSize(mesh); int totalFacesToRemove = mesh.numValidFaces; float t = Time.realtimeSinceStartup + kProgressInterval; float costThreshold = meshSize * parameters.maximumError * 0.001f; costThreshold *= costThreshold; int progressCounter = 0; while (true) { int num = Collapse(1, costThreshold); if (num == 0) { break; } progressCounter--; if (progressCounter <= 0) { progressCounter = kProgressGroups; if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null) { t = Time.realtimeSinceStartup; int facesRemoved = totalFacesToRemove - (mesh.numValidFaces - parameters.targetFaceCount); progressDelegate("Mesh Faces " + mesh.numValidFaces + "->" + parameters.targetFaceCount, 0.1f + 0.9f * ((float)facesRemoved) / ((float)totalFacesToRemove)); // TODO: do this better } } } } //Debugging.CheckMeshIntegrity(mesh); mesh.RebuildMesh(); /*float minShape = Debugging.CalculateMinTriangleShape(mesh); * Debug.LogError("MIN TRIANGLE SHAPE" + Mathf.Sqrt(minShape) + " param " + Mathf.Sqrt(sharpnessLimitSqr)); */ Cleanup(); }
public static bool CheckMeshIntegrity(MeshEdges mesh) { // Vertex linked faces int numVerts = mesh.vertCount(); int numFaces = mesh.faceCount(); int numEdges = mesh.edgeCount(); List <int>[] mVertexLinkedFaces = new List <int> [numVerts]; for (int i = 0; i < numVerts; ++i) { mVertexLinkedFaces[i] = new List <int>(); } for (int i = 0; i < numFaces; ++i) { Face f = mesh.faces[i]; if (f.valid) { for (int j = 0; j < f.cornerCount; ++j) { int vertIndex = f.v[j]; if (mesh.IsVertexValid(vertIndex)) { mVertexLinkedFaces[vertIndex].Add(i); } } } } for (int i = 0; i < numVerts; ++i) { if (mesh.IsVertexValid(i)) { IndexList test = new IndexList(4); for (int j = 0; j < mesh.vertices[i].linkedFaces.Count; ++j) { if (mesh.faces[mesh.vertices[i].linkedFaces[j]].valid) { test.Add(mesh.vertices[i].linkedFaces[j]); } } /*if (_areIntListsTheSame(mesh.vertices[i].linkedFaces, mVertexLinkedFaces[i]) == false) { * Debug.LogError("INVALID MESH vertexLinkedFaces not correct for vertex " + i); * return false; * }*/ } } // Vertex linked edges for (int i = 0; i < numVerts; ++i) { if (mesh.IsVertexValid(i)) { List <int> li1 = new List <int>(); mesh.CollectVerticesAroundVertex(i, ref li1); List <int> li2 = new List <int>(); List <int> le = mesh.linkedEdgesForVert(i); for (int j = 0; j < le.Count; ++j) { if (mesh.IsEdgeValid(le[j])) { li2.Add(mesh.edges[le[j]].OtherVertex(i)); } } if (_areIntListsTheSame(li1, li2) == false) { Debug.LogError("INVALID MESH vertexLinkedEdges not correct for vertex" + i); return(false); } } } // Edge linked faces for (int i = 0; i < numEdges; ++i) { if (mesh.IsEdgeValid(i)) { IndexList test = new IndexList(18); mesh.CollectVertexPairFaces(mesh.edges[i], test); /* if (_areIntListsTheSame(test, mesh.edges[i).linkedFaces) == false) { * Debug.LogError("INVALID MESH edgeLinkedFaces not correct for edge" + i); * return false; * }*/ } } Debug.Log("Mesh integrity is good."); return(true); }
/// <summary> /// Copy mesh data from a KrablMesh meshEdges to a Unity Mesh. /// </summary> /// <param name='meshEdges'> /// The input mesh. /// </param> /// <param name='unityMesh'> /// The output Unity Mesh. Any data it contains will be overwritten. /// </param> public static void MeshEdgesToUnityMesh(KrablMesh.MeshEdges meshEdges, UnityEngine.Mesh unityMesh) { meshEdges.InvalidateDegenerateFaces(); // Ops.TriangulateWithEdges(meshEdges); int numFaces = meshEdges.faceCount(); List <ExportVertex> exVerts = new List <ExportVertex>(numFaces * 3); List <Vector3> verts = new List <Vector3>(); List <int>[] indices = new List <int> [meshEdges.numMaterials]; List <int> vertexTable = new List <int>(); // Create a list of all vertices based on face corners (= lots of duplicates) for (int material = 0; material < meshEdges.numMaterials; ++material) { indices[material] = new List <int>(); for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) { Face f = meshEdges.faces[faceIndex]; if (f.valid && f.material == material) { int cornerCount = f.cornerCount; for (int cornerIndex = 0; cornerIndex < cornerCount; ++cornerIndex) { ExportVertex ev = new ExportVertex(); ev.vertIndex = f.v[cornerIndex]; ev.cornerCount = cornerCount; ev.material = material; ev.coords = meshEdges.vertices[f.v[cornerIndex]].coords; ev.normal = f.vertexNormal[cornerIndex]; ev.uv1 = f.uv1[cornerIndex]; ev.uv2 = f.uv2[cornerIndex]; vertexTable.Add(exVerts.Count); exVerts.Add(ev); } } } } vertexTable.Sort(delegate(int a, int b) { return(ExportVertex.CompareEV(exVerts[a], exVerts[b])); }); // Create index list to collapse list to the unique verts added to the final mesh // compile unique mesh arrays List <Vector3> normals = new List <Vector3>(); List <Vector2> uv1 = new List <Vector2>(); List <Vector2> uv2 = new List <Vector2>(); List <Color> vertColors = new List <Color>(); List <BoneWeight> boneWeights = new List <BoneWeight>(); int[] uniqueTable = new int[exVerts.Count]; int numUnique = 0; for (int i = 0; i < exVerts.Count; ++i) { int exIndex = vertexTable[i]; if (i == 0 || ExportVertex.CompareEV(exVerts[vertexTable[i - 1]], exVerts[exIndex]) != 0) { verts.Add(exVerts[exIndex].coords); normals.Add(exVerts[exIndex].normal); uv1.Add(exVerts[exIndex].uv1); uv2.Add(exVerts[exIndex].uv2); Vertex v = meshEdges.vertices[exVerts[exIndex].vertIndex]; vertColors.Add(v.color); boneWeights.Add(v.boneWeight); numUnique++; } uniqueTable[exIndex] = numUnique - 1; } // Debug.Log("Num exVerts " + exVerts.Length + " num collapsed " + numUnique + " vertsCount " + verts.Count); #if false // Quad topology seams to be broken on windows directx! Disabled it for now. if (meshEdges.topology == MeshTopology.Triangles) // quads/tris only { for (int i = 0; i < exVerts.Count; i += 3) { int mat = exVerts[i].material; indices[mat].Add(uniqueTable[i]); indices[mat].Add(uniqueTable[i + 1]); indices[mat].Add(uniqueTable[i + 2]); } } else if (meshEdges.topology == MeshTopology.Quads) { for (int i = 0; i < exVerts.Count; i += 4) { int mat = exVerts[i].material; indices[mat].Add(uniqueTable[i]); indices[mat].Add(uniqueTable[i + 1]); indices[mat].Add(uniqueTable[i + 2]); indices[mat].Add(uniqueTable[i + 3]); } } else #endif { // Mixed tris/quads need to split quads int cornerCount, mat; int i0, i1, i2, i3; for (i0 = 0; i0 < exVerts.Count;) { i1 = i0 + 1; i2 = i0 + 2; cornerCount = exVerts[i0].cornerCount; mat = exVerts[i0].material; if (cornerCount == 3) { indices[mat].Add(uniqueTable[i0]); indices[mat].Add(uniqueTable[i1]); indices[mat].Add(uniqueTable[i2]); i0 += 3; } else // Quad! { i3 = i0 + 3; Vector3 diag02 = exVerts[i0].coords - exVerts[i2].coords; Vector3 diag13 = exVerts[i1].coords - exVerts[i3].coords; if (diag02.sqrMagnitude > diag13.sqrMagnitude) { // If 0-2 if shorter than 1-3 indices[mat].Add(uniqueTable[i0]); indices[mat].Add(uniqueTable[i1]); indices[mat].Add(uniqueTable[i2]); indices[mat].Add(uniqueTable[i0]); indices[mat].Add(uniqueTable[i2]); indices[mat].Add(uniqueTable[i3]); } else { indices[mat].Add(uniqueTable[i0]); indices[mat].Add(uniqueTable[i1]); indices[mat].Add(uniqueTable[i3]); indices[mat].Add(uniqueTable[i1]); indices[mat].Add(uniqueTable[i2]); indices[mat].Add(uniqueTable[i3]); } i0 += 4; } } } unityMesh.Clear(false); unityMesh.name = "KMesh"; if (verts.Count >= 65536 || exVerts.Count >= 65536 * 3) { Debug.Log("Cannot create Unity mesh from KrablMesh.MeshEdges. " + "The mesh is too large (>65k). " + "Vertices: " + verts.Count + " Triangles: " + exVerts.Count / 3); return; } unityMesh.subMeshCount = meshEdges.numMaterials; unityMesh.vertices = verts.ToArray(); unityMesh.normals = normals.ToArray(); if (meshEdges.hasVertexColors) { unityMesh.colors = vertColors.ToArray(); } if (meshEdges.hasUV1) { unityMesh.uv = uv1.ToArray(); } if (meshEdges.hasUV2) { unityMesh.uv2 = uv2.ToArray(); } if (meshEdges.hasBoneWeights) { unityMesh.bindposes = meshEdges.bindposes; unityMesh.boneWeights = boneWeights.ToArray(); } #if false if (meshEdges.topology == MeshTopology.Quads) { for (int mat = 0; mat < meshEdges.numMaterials; ++mat) { unityMesh.SetIndices(indices[mat].ToArray(), UnityEngine.MeshTopology.Quads, mat); } } else #endif { for (int mat = 0; mat < meshEdges.numMaterials; ++mat) { unityMesh.SetTriangles(indices[mat].ToArray(), mat); } } if (meshEdges.hasUV1 && meshEdges.calculateTangents) { _calculateMeshTangents(unityMesh); } }
// TrisToQuads. Needs mesh with edgelist /// <summary> /// Joins neighbour triangles to quads in a mesh by dissolving selected edges. /// This method needs the mesh to have its edges calculated. The edges are sorted /// by their angles and dissolved in order until the maximum edge angle is reached. /// Special edges such as uv borders or material seams are not dissolved and concave quads /// are avoided. This method is used by the quad-based subdivision algorithm as it works /// much better with quads. /// </summary> /// <param name='mesh'> /// The mesh to process. /// </param> /// <param name='maximumEdgeAngle'> /// The maximum angle between two triangles to be joined to a (non-planar) quad. /// </param> public static void TrisToQuads(MeshEdges mesh, float maximumEdgeAngle) { if (mesh.topology != MeshTopology.Triangles) { return; } int i, j; mesh.topology = MeshTopology.Mixed; // Most likely we'll end up with a mixed topology as some triangles will be left. mesh.CalculateFaceNormals(); mesh.CalculateEdgeLinkedFaces(); // Calculate the edge angles and compile list of 2-face edges int numEdges = mesh.edgeCount(); List <Edge> inneredges = new List <Edge>(); for (i = 0; i < numEdges; ++i) { Edge e = mesh.edges[i] ; if (e.linkedFaces.Count == 2 && mesh.CanEdgeBeDissolved(i)) { e.mark = i; // Save the index! e.angle = mesh.CalculateEdgeAngle(i); if (e.angle < maximumEdgeAngle) { inneredges.Add(e); } } } // Sort by angle inneredges.Sort(delegate(Edge a, Edge b) { return(a.angle.CompareTo(b.angle)); }); int iecount = inneredges.Count; // Debug.Log("Number of inneredges " + iecount); Vector3[] qVec = new Vector3[5]; for (i = 0; i < iecount; ++i) { Edge e = inneredges[i]; Face f1 = mesh.faces[e.linkedFaces[0]]; Face f2 = mesh.faces[e.linkedFaces[1]]; // check if edge is between two triangles if (f1.valid && f2.valid && (f1.cornerCount == 3) && (f2.cornerCount == 3)) { // Make sure this doesn't lead to a concave quad Vector3 newNormal = f1.normal + f2.normal; //newNormal.Normalize(); // Collect all vertex coordinates qVec[0] = mesh.vertices[e.v[0]].coords; qVec[2] = mesh.vertices[e.v[1]].coords; for (j = 0; j < 3; ++j) { if (e.ContainsVertex(f1.v[j]) == false) { qVec[1] = mesh.vertices[f1.v[j]].coords; break; } } for (j = 0; j < 3; ++j) { if (e.ContainsVertex(f2.v[j]) == false) { qVec[3] = mesh.vertices[f2.v[j]].coords; break; } } // Flip order if it doesn't conform to f1 if (f1.VerticesInOrder(e.v[0], e.v[1]) == true) { Vector3 temp = qVec[1]; qVec[1] = qVec[3]; qVec[3] = temp; } // calculate edge vectors qVec[4] = qVec[0]; for (j = 0; j < 4; ++j) { qVec[j] -= qVec[j + 1]; } qVec[4] = qVec[0]; bool convex = true; for (j = 0; j < 4; ++j) { Vector3 localN = Vector3.Cross(qVec[j], qVec[j + 1]); //localN.Normalize(); float nAngleCos = Vector3.Dot(newNormal, localN); //Debug.Log("Angle " + nAngleCod); if (nAngleCos <= 0.0f) { convex = false; break; } } if (convex) { // Debug.Log("Dissolving edge"); mesh.DissolveEdgeTriangles(e.mark); } else { // Debug.Log("not dissolving edge -> concave quad"); } } } // There are now invalid faces and edges mesh.RebuildMesh(); mesh.GenerateEdgeTopology(); }
/// <summary> /// Copies a Unity Mesh to a KrablMesh.MeshEdges. /// </summary> /// <param name='unityMesh'> /// The Unity Mesh to use as input. /// </param> /// <param name='meshEdges'> /// The KrablMesh.meshEdges to fill with the data from the input mesh. Needs to be empty. /// </param> /// <param name='tolerance'> /// The maximum difference between two values (vertex coordinates, normal coordinates) to treat as begin equal. /// Some modelling software outputs float values that are only almost the same when they should be the same. /// In this case using a tolerance of about 1e-5f can fix problems. /// </param> public static void UnityMeshToMeshEdges(UnityEngine.Mesh unityMesh, KrablMesh.MeshEdges meshEdges, float tolerance = 0.0f) { Vector3[] verts = unityMesh.vertices; Vector3[] normals = unityMesh.normals; Color[] vertColors = unityMesh.colors; BoneWeight[] boneWeights = unityMesh.boneWeights; Vector2[] uv1 = unityMesh.uv; Vector2[] uv2 = unityMesh.uv2; meshEdges.Clear(); meshEdges.numMaterials = unityMesh.subMeshCount; meshEdges.equalityTolerance = tolerance; int numVerts = verts.Length; meshEdges.hasVertexColors = (vertColors.Length == numVerts); meshEdges.bindposes = unityMesh.bindposes; meshEdges.hasBoneWeights = (meshEdges.bindposes != null && boneWeights.Length == numVerts); meshEdges.hasUV1 = (uv1.Length == numVerts); meshEdges.hasUV2 = (uv2.Length == numVerts); int i; for (i = 0; i < numVerts; ++i) { meshEdges.AddVertex(verts[i]); } if (meshEdges.hasVertexColors) { for (i = 0; i < numVerts; ++i) { meshEdges.vertices[i].color = vertColors[i]; } } if (meshEdges.hasBoneWeights) { for (i = 0; i < numVerts; ++i) { meshEdges.vertices[i].boneWeight = boneWeights[i]; } } // Figure out if this is a unity quad mesh. // theoretically this could be different per material bool quad = true; for (int m = 0; m < meshEdges.numMaterials; ++m) { if (unityMesh.GetTopology(m) != UnityEngine.MeshTopology.Quads) { quad = false; break; } } int v0, v1, v2, v3; for (int m = 0; m < meshEdges.numMaterials; ++m) { if (quad) { int[] indices = unityMesh.GetIndices(m); int num = indices.Length; for (i = 0; i < num;) { v0 = indices[i++]; v1 = indices[i++]; v2 = indices[i++]; v3 = indices[i++]; Face f = new Face(v0, v1, v2, v3); meshEdges.AddFace(f); f.vertexNormal[0] = normals[v0]; f.vertexNormal[1] = normals[v1]; f.vertexNormal[2] = normals[v2]; f.vertexNormal[3] = normals[v3]; if (meshEdges.hasUV1) { f.uv1[0] = uv1[v0]; f.uv1[1] = uv1[v1]; f.uv1[2] = uv1[v2]; f.uv1[3] = uv1[v3]; } if (meshEdges.hasUV2) { f.uv2[0] = uv2[v0]; f.uv2[1] = uv2[v1]; f.uv2[2] = uv2[v2]; f.uv2[3] = uv2[v3]; } f.material = m; } } else { int[] tris = unityMesh.GetTriangles(m); int num = tris.Length; for (i = 0; i < num;) { v0 = tris[i++]; v1 = tris[i++]; v2 = tris[i++]; Face f = new Face(v0, v1, v2); meshEdges.AddFace(f); f.vertexNormal[0] = normals[v0]; f.vertexNormal[1] = normals[v1]; f.vertexNormal[2] = normals[v2]; if (meshEdges.hasUV1) { f.uv1[0] = uv1[v0]; f.uv1[1] = uv1[v1]; f.uv1[2] = uv1[v2]; } if (meshEdges.hasUV2) { f.uv2[0] = uv2[v0]; f.uv2[1] = uv2[v1]; f.uv2[2] = uv2[v2]; } f.material = m; } } } KrablMesh.Ops.RemoveDoubleVertices(meshEdges); meshEdges.GenerateEdgeList(); meshEdges.CalculateEdgeLinkedFaces(); meshEdges.topology = quad ? MeshTopology.Quads : MeshTopology.Triangles; KrablMesh.CreaseDetect.MarkCreasesFromFaceNormals(meshEdges); }
public void Subdivide(ref MeshEdges mesh) { int i, j; int numVerts = mesh.vertCount(); int numFaces = mesh.faceCount(); int numEdges = mesh.edgeCount(); mesh.CalculateEdgeLinkedFaces(); _calculateVertexPositions(mesh); // TODO:don't generate a new mesh... just add the verts to the old mesh = faster and uses less memory MeshEdges newMesh = new MeshEdges(); int faceOffset = numVerts; int edgeOffset = numVerts + numFaces; float[] edgeRatio = new float[numEdges]; // Add vertices to new mesh, precalculate stuff for (i = 0; i < numVerts; ++i) { newMesh.AddVertex(vertPoints[i]); } for (i = 0; i < numFaces; ++i) { newMesh.AddVertex(facePoints[i]); } for (i = 0; i < numEdges; ++i) { int vertexIndex = newMesh.AddVertex(edgePoints[i]); Vertex nv = newMesh.vertices[vertexIndex]; Vertex ov0 = mesh.vertices[mesh.edges[i].v[0]]; Vertex ov1 = mesh.vertices[mesh.edges[i].v[1]]; edgeRatio[i] = KrablMesh.UnityUtils.ProjectedRatioOfPointOnVector(nv.coords, ov0.coords, ov1.coords); } if (mesh.hasBoneWeights) { for (i = 0; i < numVerts; ++i) { newMesh.vertices[i].boneWeight = mesh.vertices[i].boneWeight; } for (i = 0; i < numEdges; ++i) { int[] vi = mesh.edges[i].v; newMesh.vertices[edgeOffset + i].boneWeight = KrablMesh.UnityUtils.BoneWeightLerp( mesh.vertices[vi[0]].boneWeight, mesh.vertices[vi[1]].boneWeight, edgeRatio[i]); } for (i = 0; i < numFaces; ++i) { newMesh.vertices[faceOffset + i].boneWeight = mesh.CalculateFaceCenterBoneWeight(i); } } if (mesh.hasVertexColors) { for (i = 0; i < numVerts; ++i) { newMesh.vertices[i].color = mesh.vertices[i].color; } for (i = 0; i < numEdges; ++i) { int[] vi = mesh.edges[i].v; newMesh.vertices[edgeOffset + i].color = Color.Lerp( mesh.vertices[vi[0]].color, mesh.vertices[vi[1]].color, edgeRatio[i]); } for (i = 0; i < numFaces; ++i) { newMesh.vertices[faceOffset + i].color = mesh.CalculateFaceCenterColor(i); } } // Create the faces Face nf, of; int g, h; int a, b, c, d; for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) { of = mesh.faces[faceIndex]; if (of.valid) { h = of.cornerCount - 1; // scan through with indices g, h, i g = of.cornerCount - 2; Vector2 centerUV1 = mesh.CalculateFaceCenterUV1(faceIndex); Vector2 centerUV2 = mesh.CalculateFaceCenterUV2(faceIndex); Vector3 centerVertexNormal = mesh.CalculateFaceCenterVertexNormal(faceIndex); for (i = 0; i < of.cornerCount; ++i) { int edgeIndex0 = mesh.EdgeIndexForVertices(of.v[h], of.v[i]); if (edgeIndex0 < 0) { continue; } float ratio0 = edgeRatio[edgeIndex0]; if (mesh.edges[edgeIndex0].v[0] == of.v[h]) { a = h; b = i; // interpolation indexes. 1- ratio gives inaccurate results (floating point problems) } else { a = i; b = h; } int edgeIndex1 = mesh.EdgeIndexForVertices(of.v[g], of.v[h]); if (edgeIndex1 < 0) { continue; } float ratio1 = edgeRatio[edgeIndex1]; if (mesh.edges[edgeIndex1].v[0] == of.v[g]) { c = g; d = h; } else { c = h; d = g; } nf = new Face( of.v[h], // corner point edgeOffset + edgeIndex0, faceOffset + faceIndex, // center point edgeOffset + edgeIndex1 ); nf.uv1[0] = of.uv1[h]; nf.uv1[1] = Vector2.Lerp(of.uv1[a], of.uv1[b], ratio0); nf.uv1[2] = centerUV1; nf.uv1[3] = Vector2.Lerp(of.uv1[c], of.uv1[d], ratio1); if (mesh.hasUV2) { nf.uv2[0] = of.uv2[h]; nf.uv2[1] = Vector2.Lerp(of.uv2[a], of.uv2[b], ratio0); nf.uv2[2] = centerUV2; nf.uv2[3] = Vector2.Lerp(of.uv2[c], of.uv2[d], ratio1); } if (!recalculateNormals) { nf.vertexNormal[0] = of.vertexNormal[h]; nf.vertexNormal[1] = Vector3.Lerp(of.vertexNormal[a], of.vertexNormal[b], ratio0); nf.vertexNormal[2] = centerVertexNormal; nf.vertexNormal[3] = Vector3.Lerp(of.vertexNormal[c], of.vertexNormal[d], ratio1); } nf.material = of.material; newMesh.AddFace(nf); g = h; h = i; } } } newMesh.hasUV1 = mesh.hasUV1; newMesh.hasUV2 = mesh.hasUV2; newMesh.hasBoneWeights = mesh.hasBoneWeights; newMesh.bindposes = mesh.bindposes; newMesh.numMaterials = mesh.numMaterials; newMesh.hasVertexColors = mesh.hasVertexColors; newMesh.topology = MeshTopology.Quads; newMesh.GenerateEdgeList(); newMesh.GenerateEdgeTopology(); // Need to copy edge information from the original mesh to the new mesh // Every edge of the original mesh now has two parts int edgePointIndex; for (i = 0; i < numEdges; ++i) { Edge e = mesh.edges[i]; edgePointIndex = edgeOffset + i; // as newmesh was just constructed, we know the correct vertex index of the edge center point! for (j = 0; j < 2; ++j) { int eindex = newMesh.EdgeIndexForVertices(e.v[j], edgePointIndex); // Copy attributes (just crease for now) newMesh.edges[eindex].crease = e.crease; } } mesh = newMesh; }
void _calculateVertexPositions(MeshEdges mesh) { int numVerts = mesh.vertCount(); int numFaces = mesh.faceCount(); int numEdges = mesh.edgeCount(); vertPoints = new Vector3[numVerts]; facePoints = new Vector3[numFaces]; edgePoints = new Vector3[numEdges]; // In any case the new face points are the center of the faces for (int i = 0; i < numFaces; ++i) { facePoints[i] = mesh.CalculateFaceCenter(i); } if (smooth) { // First count the number of creases connected to each vertex int[] vertexNumCreases = new int[numVerts]; for (int i = 0; i < numVerts; ++i) { List <int> linkedEdges = mesh.linkedEdgesForVert(i); for (int j = 0; j < linkedEdges.Count; ++j) { int edgeIndex = linkedEdges[j]; if (mesh.IsEdgeValid(edgeIndex) && mesh.edges[edgeIndex].crease > 0.0) { vertexNumCreases[i]++; } } } // Edge is the average of the two ends and the connected face centers for (int i = 0; i < numEdges; ++i) { Edge e = mesh.edges[i]; IndexList linkedFaces = e.linkedFaces; edgePoints[i] = mesh.CalculateVertexPairCenter(e); // If an edge connects two verts that do not move, use the center to prevent overlaps //bool betweenNonMovableVertices = (vertexNumCreases[e.v[0]] > 2 && vertexNumCreases[e.v[1]] > 2); //if (betweenNonMovableVertices) Debug.Log("Between NONmovable verts"); if (/*betweenNonMovableVertices == false &&*/ e.crease == 0.0f && linkedFaces.Count >= 2) { // creases and borders stay at edge centers // other edges use the edge center + the center of all attached face centers Vector3 faceCenterCenter = facePoints[linkedFaces[0]]; int numLinked = e.linkedFaces.Count; for (int j = 1; j < numLinked; ++j) { faceCenterCenter += facePoints[linkedFaces[j]]; } edgePoints[i] = 0.5f * (edgePoints[i] + faceCenterCenter * (1.0f / ((float)numLinked))); } } // Vert for (int i = 0; i < numVerts; ++i) { Vector3 oldPosition = mesh.vertices[i].coords; List <int> linkedEdges = mesh.linkedEdgesForVert(i); if (_isVertexBorder(mesh, i) == false) { // First deal with edges. the number of creases needs to be known! Vector3 edgesCenter = Vector3.zero; float numCreases = 0.0f; Vector3 creaseCenter = Vector3.zero; float nEdges = 0.0f; for (int j = 0; j < linkedEdges.Count; ++j) { int edgeIndex = linkedEdges[j]; if (mesh.IsEdgeValid(edgeIndex)) { Edge e = mesh.edges[edgeIndex]; Vector3 center = mesh.CalculateVertexPairCenter(e); if (e.crease > 0.0f) { numCreases += 1.0f; creaseCenter += center; } nEdges += 1.0f; edgesCenter += center; } } edgesCenter *= 1.0f / nEdges; // For points without crease edges or just one -> do nothing if (numCreases == 2.0f) { // like a border connection vertPoints[i] = 0.5f * oldPosition + creaseCenter * (0.5f / numCreases); } else if (numCreases > 2.0f) { // A sharp corner vertPoints[i] = oldPosition; } else { // Full formula including faces center which needs to be calculated IndexList linkedFaces = mesh.vertices[i].linkedFaces; Vector3 facesCenter = Vector3.zero; float n = 0.0f; for (int j = 0; j < linkedFaces.Count; ++j) { int faceIndex = linkedFaces[j]; if (mesh.faces[faceIndex].valid) { facesCenter += facePoints[faceIndex]; n += 1.0f; } } float invN = 1.0f / n; vertPoints[i] = (facesCenter * invN + 2.0f * edgesCenter + (n - 3.0f) * oldPosition) * invN; // useless for 2 creases!!! } } else { // Border Vertex. get center of all connected border centers. if (vertexNumCreases[i] > 2) { vertPoints[i] = oldPosition; } else { float nBorders = 0.0f; Vector3 borderCenter = Vector3.zero; for (int j = 0; j < linkedEdges.Count; ++j) { int edgeIndex = linkedEdges[j]; if (mesh.IsEdgeValid(edgeIndex)) { Edge e = mesh.edges[edgeIndex]; if (mesh.IsEdgeBorder(edgeIndex)) { borderCenter += mesh.CalculateVertexPairCenter(e); nBorders += 1.0f; } } } vertPoints[i] = 0.5f * oldPosition + borderCenter * (0.5f / nBorders); } } } } else { // Subdivision without smoothing for (int i = 0; i < numVerts; ++i) { vertPoints[i] = mesh.vertices[i].coords; } for (int i = 0; i < numEdges; ++i) { edgePoints[i] = mesh.CalculateVertexPairCenter(mesh.edges[i]); } } }