/////////////////////////////////////////////////////////////////////////// #region [Nearpoint] /** * Find the point for which attribute namde 'attrName' is closest to 'value'. * NB: If this was going to be repeated a lot of times, consider building * a KDTree (not included in BMeshOperators yet). */ public static Vertex Nearpoint(BMesh mesh, AttributeValue value, string attrName) { if (!mesh.HasVertexAttribute(attrName)) { return(null); } Vertex argmin = null; float min = 0; foreach (Vertex v in mesh.vertices) { float d = AttributeValue.Distance(v.attributes[attrName], value); if (argmin == null || d < min) { argmin = v; min = d; } } return(argmin); }
/** * Try to make quads as square as possible (may be called iteratively). * This is not a very common operation but was developped so I keep it here. * This assumes that the mesh is only made of quads. * Overriding attributes: vertex's id * Optionnaly read vertexattributes: * - restpos: a Float3 telling which position attracts the vertex * - weight: a Float telling to which extent the restpos must be * considered. * * @param rate speed at which faces are squarified. A higher rate goes * faster but there is a risk for overshooting. * @param uniformLength whether the size of the quads must be uniformized. */ public static void SquarifyQuads(BMesh mesh, float rate = 1.0f, bool uniformLength = false) { float avg = 0; if (uniformLength) { avg = AverageRadiusLength(mesh); } var pointUpdates = new Vector3[mesh.vertices.Count]; var weights = new float[mesh.vertices.Count]; int i = 0; foreach (Vertex v in mesh.vertices) { if (mesh.HasVertexAttribute("restpos")) { if (mesh.HasVertexAttribute("weight")) { weights[i] = (v.attributes["weight"] as FloatAttributeValue).data[0]; } else { weights[i] = 1; } var restpos = (v.attributes["restpos"] as FloatAttributeValue).AsVector3(); pointUpdates[i] = (restpos - v.point) * weights[i]; } else { pointUpdates[i] = Vector3.zero; weights[i] = 0; } v.id = i++; } // Accumulate updates foreach (Face f in mesh.faces) { Vector3 c = f.Center(); List <Vertex> verts = f.NeighborVertices(); if (verts.Count != 4) { continue; } // (r for "radius") Vector3 r0 = verts[0].point - c; Vector3 r1 = verts[1].point - c; Vector3 r2 = verts[2].point - c; Vector3 r3 = verts[3].point - c; var localToGlobal = ComputeLocalAxis(r0, r1, r2, r3); var globalToLocal = localToGlobal.transpose; // in local coordinates (l for "local") Vector3 l0 = globalToLocal * r0; Vector3 l1 = globalToLocal * r1; Vector3 l2 = globalToLocal * r2; Vector3 l3 = globalToLocal * r3; bool switch03 = false; if (l1.normalized.y < l3.normalized.y) { switch03 = true; var tmp = l3; l3 = l1; l1 = tmp; } // now 0->1->2->3 is in direct trigonometric order // Rotate vectors (rl for "rotated local") Vector3 rl0 = l0; Vector3 rl1 = new Vector3(l1.y, -l1.x, l1.z); Vector3 rl2 = new Vector3(-l2.x, -l2.y, l2.z); Vector3 rl3 = new Vector3(-l3.y, l3.x, l3.z); Vector3 average = (rl0 + rl1 + rl2 + rl3) / 4; if (uniformLength) { average = average.normalized * avg; } // Rotate back (lt for "local target") Vector3 lt0 = average; Vector3 lt1 = new Vector3(-average.y, average.x, average.z); Vector3 lt2 = new Vector3(-average.x, -average.y, average.z); Vector3 lt3 = new Vector3(average.y, -average.x, average.z); // Switch back if (switch03) { var tmp = lt3; lt3 = lt1; lt1 = tmp; } // Back to global (t for "target") Vector3 t0 = localToGlobal * lt0; Vector3 t1 = localToGlobal * lt1; Vector3 t2 = localToGlobal * lt2; Vector3 t3 = localToGlobal * lt3; // Accumulate pointUpdates[verts[0].id] += t0 - r0; pointUpdates[verts[1].id] += t1 - r1; pointUpdates[verts[2].id] += t2 - r2; pointUpdates[verts[3].id] += t3 - r3; weights[verts[0].id] += 1; weights[verts[1].id] += 1; weights[verts[2].id] += 1; weights[verts[3].id] += 1; } // Apply updates i = 0; foreach (Vertex v in mesh.vertices) { if (weights[i] > 0) { v.point += pointUpdates[i] * (rate / weights[i]); } ++i; } }
/////////////////////////////////////////////////////////////////////////// #region [SetInMeshFilter] /** * Convert a BMesh into a Unity Mesh and set it in the provided MeshFilter * WARNING: Only works with tri or quad meshes! * read attributes uv, uv2 from vertices and materialId from faces. * * NB: UVs are read from vertices, because in a Unity mesh when two face * corners have different UVs, they are different vertices. If you worked * with UVs as Loop attributes, you must first split points and migrate UVs * to vertex attributes. */ public static void SetInMeshFilter(BMesh mesh, MeshFilter mf) { // Points Vector2[] uvs = null; Vector2[] uvs2 = null; Vector3[] normals = null; Color[] colors = null; Vector3[] points = new Vector3[mesh.vertices.Count]; if (mesh.HasVertexAttribute("uv")) { uvs = new Vector2[mesh.vertices.Count]; } if (mesh.HasVertexAttribute("uv2")) { uvs2 = new Vector2[mesh.vertices.Count]; } if (mesh.HasVertexAttribute("normal")) { normals = new Vector3[mesh.vertices.Count]; } if (mesh.HasVertexAttribute("color")) { colors = new Color[mesh.vertices.Count]; } int i = 0; foreach (var vert in mesh.vertices) { vert.id = i; points[i] = vert.point; if (uvs != null) { var uv = vert.attributes["uv"] as FloatAttributeValue; uvs[i] = new Vector2(uv.data[0], uv.data[1]); } if (uvs2 != null) { var uv2 = vert.attributes["uv2"] as FloatAttributeValue; uvs2[i] = new Vector2(uv2.data[0], uv2.data[1]); } if (normals != null) { var normal = vert.attributes["normal"] as FloatAttributeValue; normals[i] = normal.AsVector3(); } if (colors != null) { var color = vert.attributes["color"] as FloatAttributeValue; colors[i] = color.AsColor(); } ++i; } // Triangles int maxMaterialId = 0; bool hasMaterialAttr = mesh.HasFaceAttribute("materialId"); if (hasMaterialAttr) { foreach (var f in mesh.faces) { maxMaterialId = Mathf.Max(maxMaterialId, f.attributes["materialId"].asInt().data[0]); } } int[] tricounts = new int[maxMaterialId + 1]; foreach (var f in mesh.faces) { Debug.Assert(f.vertcount == 3 || f.vertcount == 4, "Only meshes with triangles/quads can be converted to a unity mesh"); int mat = hasMaterialAttr ? f.attributes["materialId"].asInt().data[0] : 0; tricounts[mat] += f.vertcount - 2; } int[][] triangles = new int[maxMaterialId + 1][]; for (int mat = 0; mat < triangles.Length; ++mat) { triangles[mat] = new int[3 * tricounts[mat]]; tricounts[mat] = 0; } // from now on tricounts[i] is the index of the next triangle to fill in the i-th triangle list foreach (var f in mesh.faces) { int mat = hasMaterialAttr ? f.attributes["materialId"].asInt().data[0] : 0; Debug.Assert(f.vertcount == 3 || f.vertcount == 4); { var l = f.loop; triangles[mat][3 * tricounts[mat] + 0] = l.vert.id; l = l.next; triangles[mat][3 * tricounts[mat] + 2] = l.vert.id; l = l.next; triangles[mat][3 * tricounts[mat] + 1] = l.vert.id; l = l.next; ++tricounts[mat]; } if (f.vertcount == 4) { var l = f.loop.next.next; triangles[mat][3 * tricounts[mat] + 0] = l.vert.id; l = l.next; triangles[mat][3 * tricounts[mat] + 2] = l.vert.id; l = l.next; triangles[mat][3 * tricounts[mat] + 1] = l.vert.id; l = l.next; ++tricounts[mat]; } } // Apply mesh Mesh unityMesh = new Mesh(); mf.mesh = unityMesh; unityMesh.vertices = points; if (uvs != null) { unityMesh.uv = uvs; } if (uvs2 != null) { unityMesh.uv2 = uvs2; } if (normals != null) { unityMesh.normals = normals; } if (colors != null) { unityMesh.colors = colors; } unityMesh.subMeshCount = triangles.Length; // Fix an issue when renderer has more materials than there are submeshes var renderer = mf.GetComponent <MeshRenderer>(); if (renderer) { unityMesh.subMeshCount = Mathf.Max(unityMesh.subMeshCount, renderer.materials.Length); } for (int mat = 0; mat < triangles.Length; ++mat) { unityMesh.SetTriangles(triangles[mat], mat); } if (normals == null) { unityMesh.RecalculateNormals(); } }