/////////////////////////////////////////////////////////////////////////// #region [Merge} /** * Add all vertices/edges/faces from another mesh, and fix attributes if * needed. * Overriding attributes: vertex's id (of the first mesh only) */ public static void Merge(BMesh mesh, BMesh other) { var newVerts = new Vertex[other.vertices.Count]; int i = 0; foreach (Vertex v in other.vertices) { newVerts[i] = mesh.AddVertex(v.point); AttributeLerp(mesh, newVerts[i], v, v, 1); // copy all attributes v.id = i; ++i; } foreach (Edge e in other.edges) { mesh.AddEdge(newVerts[e.vert1.id], newVerts[e.vert2.id]); } foreach (Face f in other.faces) { var neighbors = f.NeighborVertices(); var newNeighbors = new Vertex[neighbors.Count]; int j = 0; foreach (var v in neighbors) { newNeighbors[j] = newVerts[v.id]; ++j; } mesh.AddFace(newNeighbors); } }
static bool TestSubdivideQuad() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); mesh.AddFace(v0, v1, v2, v3); BMeshOperators.Subdivide(mesh); Debug.Assert(mesh.vertices.Count == 9, "vertex count"); Debug.Assert(mesh.edges.Count == 12, "edge count"); Debug.Assert(mesh.loops.Count == 16, "loop count"); Debug.Assert(mesh.faces.Count == 4, "face count"); foreach (Face f in mesh.faces) { Debug.Assert(f.vertcount == 4, "faces are quads"); } Debug.Log("TestBMeshOperators TestSubdivideQuad passed."); return(true); }
static bool TestAttributeLerp() { var mesh = new BMesh(); mesh.AddVertexAttribute(new AttributeDefinition("uv", AttributeBaseType.Float, 2)); mesh.AddVertexAttribute(new AttributeDefinition("mat", AttributeBaseType.Int, 1)); Vertex v0 = mesh.AddVertex(new Vector3(0, 0, 0)); Vertex v1 = mesh.AddVertex(new Vector3(0, 0, 0)); Vertex v2 = mesh.AddVertex(new Vector3(0, 0, 0)); v0.attributes["uv"] = new FloatAttributeValue(0.12f, 0.0f); v2.attributes["uv"] = new FloatAttributeValue(0.33f, 1.0f); v0.attributes["mat"] = new IntAttributeValue(0); v2.attributes["mat"] = new IntAttributeValue(1); BMeshOperators.AttributeLerp(mesh, v1, v0, v2, 0.4f); var uv1 = v1.attributes["uv"] as FloatAttributeValue; var mat1 = v1.attributes["mat"] as IntAttributeValue; Debug.Assert(uv1.data.Length == 2 && uv1.data[0] == Mathf.Lerp(0.12f, 0.33f, 0.4f) && uv1.data[1] == 0.4f, "interpolate uv"); Debug.Assert(mat1.data.Length == 1 && mat1.data[0] == 0, "interpolate mat"); Debug.Log("TestBMeshOperators TestAttributeLerp passed."); return(true); }
static bool Test3() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); Face f0 = mesh.AddFace(v0, v1, v2); Face f1 = mesh.AddFace(v2, v1, v3); Debug.Assert(mesh.vertices.Count == 4, "vert count"); Debug.Assert(mesh.loops.Count == 6, "loop count"); Debug.Assert(mesh.edges.Count == 5, "edge count"); Debug.Assert(mesh.faces.Count == 2, "face count"); Debug.Assert(v0.NeighborFaces().Count == 1, "v0 has one neighbor face (found count: " + v0.NeighborFaces().Count + ")"); Debug.Assert(v1.NeighborFaces().Count == 2, "v1 has two neighbor face (found count: " + v1.NeighborFaces().Count + ")"); foreach (Loop l in mesh.loops) { Debug.Assert(l.next != null, "loop has a next loop"); Debug.Assert(l.prev != null, "loop has a next loop"); } Debug.Assert(f0.Loop(v0) != null, "loop with vertex v0 does not exist in face f0"); Debug.Assert(f0.Loop(v0).vert == v0, "loop with vertex v0 has v0 as corner"); Debug.Assert(f0.Loop(v1) != null, "loop with vertex v1 does not exist in face f0"); Debug.Assert(f0.Loop(v1).vert == v1, "loop with vertex v1 has v1 as corner"); Debug.Assert(f0.Loop(v3) == null, "loop with vertex v3 does not exist in face f0"); Edge e0 = null; foreach (Edge e in mesh.edges) { if ((e.vert1 == v1 && e.vert2 == v2) || (e.vert1 == v2 && e.vert2 == v1)) { e0 = e; break; } } Debug.Assert(e0 != null, "found edge between v1 and v2"); mesh.RemoveEdge(e0); Debug.Assert(mesh.vertices.Count == 4, "vert count after removing edge"); Debug.Assert(mesh.loops.Count == 0, "loop count after removing edge"); Debug.Assert(mesh.edges.Count == 4, "edge count after removing edge"); Debug.Assert(mesh.faces.Count == 0, "face count after removing edge"); foreach (Loop l in mesh.loops) { Debug.Assert(l.next != null, "loop still has a next loop"); Debug.Assert(l.prev != null, "loop still has a next loop"); } Debug.Log("TestBMesh #3 passed."); return(true); }
/////////////////////////////////////////////////////////////////////////// #region [AttributeLerp] /** * Set all attributes in destination vertex to attr[v1] * (1 - t) + attr[v2] * t * Overriding attributes: all in vertex 'destination', none in others. */ public static void AttributeLerp(BMesh mesh, Vertex destination, Vertex v1, Vertex v2, float t) { foreach (var attr in mesh.vertexAttributes) { if (!v1.attributes.ContainsKey(attr.name) || !v2.attributes.ContainsKey(attr.name)) { continue; } switch (attr.type.baseType) { case AttributeBaseType.Float: { var val1 = v1.attributes[attr.name] as FloatAttributeValue; var val2 = v2.attributes[attr.name] as FloatAttributeValue; int n = val1.data.Length; Debug.Assert(val2.data.Length == n); var val = new FloatAttributeValue { data = new float[n] }; for (int i = 0; i < n; ++i) { val.data[i] = Mathf.Lerp(val1.data[i], val2.data[i], t); } destination.attributes[attr.name] = val; break; } case AttributeBaseType.Int: { var val1 = v1.attributes[attr.name] as IntAttributeValue; var val2 = v2.attributes[attr.name] as IntAttributeValue; int n = val1.data.Length; Debug.Assert(val2.data.Length == n); var val = new IntAttributeValue { data = new int[n] }; for (int i = 0; i < n; ++i) { val.data[i] = (int)Mathf.Round(Mathf.Lerp(val1.data[i], val2.data[i], t)); } destination.attributes[attr.name] = val; break; } default: Debug.Assert(false); break; } } }
/////////////////////////////////////////////////////////////////////////// #region [Subdivide] /** * Subdivide a mesh, without smoothing it, trying to interpolate all * available attributes as much as possible. After subdivision, all faces * are quads. * Overriding attributes: edge's id */ public static void Subdivide(BMesh mesh) { int i = 0; var edgeCenters = new Vertex[mesh.edges.Count]; var originalEdges = new Edge[mesh.edges.Count]; foreach (Edge e in mesh.edges) { edgeCenters[i] = mesh.AddVertex(e.Center()); AttributeLerp(mesh, edgeCenters[i], e.vert1, e.vert2, 0.5f); originalEdges[i] = e; e.id = i++; } var originalFaces = new List <Face>(mesh.faces); // copy because mesh.faces changes during iterations foreach (Face f in originalFaces) { Vertex faceCenter = mesh.AddVertex(f.Center()); float w = 0; // Create one quad per loop in the original face Loop it = f.loop; do { w += 1; AttributeLerp(mesh, faceCenter, faceCenter, it.vert, 1 / w); var quad = new Vertex[] { it.vert, edgeCenters[it.edge.id], faceCenter, edgeCenters[it.prev.edge.id] }; mesh.AddFace(quad); it = it.next; } while (it != f.loop); // then get rid of the original face mesh.RemoveFace(f); } // Remove old edges foreach (Edge e in originalEdges) { mesh.RemoveEdge(e); } }
/////////////////////////////////////////////////////////////////////////// #region [DrawGizmos] /** * Draw details about the BMesh structure un the viewport. * To be used inside of OnDrawGizmozs() in a MonoBehavior script. * You'll most likely need to add beforehand: * Gizmos.matrix = transform.localToWorldMatrix */ public static void DrawGizmos(BMesh mesh) { Gizmos.color = Color.yellow; foreach (var e in mesh.edges) { Gizmos.DrawLine(e.vert1.point, e.vert2.point); } Gizmos.color = Color.red; foreach (var l in mesh.loops) { Vertex vert = l.vert; Vertex other = l.edge.OtherVertex(vert); Gizmos.DrawRay(vert.point, (other.point - vert.point) * 0.1f); Loop nl = l.next; Vertex nother = nl.edge.ContainsVertex(vert) ? nl.edge.OtherVertex(vert) : nl.edge.OtherVertex(other); Vector3 no = vert.point + (other.point - vert.point) * 0.1f; Gizmos.DrawRay(no, (nother.point - no) * 0.1f); } Gizmos.color = Color.green; int i = 0; foreach (var f in mesh.faces) { Vector3 c = f.Center(); Gizmos.DrawLine(c, f.loop.vert.point); Gizmos.DrawRay(c, (f.loop.next.vert.point - c) * 0.2f); #if UNITY_EDITOR //Handles.Label(c, "f" + i); ++i; #endif // UNITY_EDITOR } i = 0; foreach (Vertex v in mesh.vertices) { #if UNITY_EDITOR //var uv = v.attributes["uv"] as BMesh.FloatAttributeValue; //Handles.Label(v.point, "" + i); ++i; #endif // UNITY_EDITOR } }
/////////////////////////////////////////////////////////////////////////// #region [Merge] /** * Merge a Unity Mesh into a BMesh. Can be used with an empty BMesh to * create a BMesh from a Unity Mesh * TODO: Add support for uvs etc. */ public static void Merge(BMesh mesh, Mesh unityMesh, bool flipFaces = false) { Vector3[] unityVertices = unityMesh.vertices; int[] unityTriangles = unityMesh.triangles; var verts = new Vertex[unityVertices.Length]; for (int i = 0; i < unityVertices.Length; ++i) { Vector3 p = unityVertices[i]; verts[i] = mesh.AddVertex(p); } for (int i = 0; i < unityTriangles.Length / 3; ++i) { mesh.AddFace( verts[unityTriangles[3 * i + (flipFaces ? 1 : 0)]], verts[unityTriangles[3 * i + (flipFaces ? 0 : 1)]], verts[unityTriangles[3 * i + 2]] ); } }
/////////////////////////////////////////////////////////////////////////// #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); }
static float AverageRadiusLength(BMesh mesh) { float lengthsum = 0; float weightsum = 0; 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; // 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; lengthsum += average.magnitude; weightsum += 1; } return(lengthsum / weightsum); }
static bool TestTwoVertexFaces() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Face f0 = mesh.AddFace(v0, v1); Face f1 = mesh.AddFace(v1, v2); Debug.Assert(mesh.vertices.Count == 3, "vert count"); Debug.Assert(mesh.loops.Count == 4, "loop count"); Debug.Assert(mesh.edges.Count == 2, "edge count"); Debug.Assert(mesh.faces.Count == 2, "face count"); Debug.Assert(v0.NeighborFaces().Count == 1, "v0 has one neighbor face (found count: " + v0.NeighborFaces().Count + ")"); Debug.Assert(v1.NeighborFaces().Count == 2, "v1 has two neighbor face (found count: " + v1.NeighborFaces().Count + ")"); Debug.Log("TestBMesh TestTwoVertexFaces passed."); return(true); }
static bool TestSubdivideTris() { var mesh = new BMesh(); mesh.AddVertexAttribute(new AttributeDefinition("uv", AttributeBaseType.Float, 2)); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); Face f0 = mesh.AddFace(v0, v1, v2); Face f1 = mesh.AddFace(v2, v1, v3); foreach (var v in mesh.vertices) { v.attributes["uv"] = new FloatAttributeValue(v.point.x, v.point.z); } BMeshOperators.Subdivide(mesh); Debug.Assert(mesh.vertices.Count == 11, "vertex count"); Debug.Assert(mesh.edges.Count == 16, "edge count"); Debug.Assert(mesh.loops.Count == 24, "loop count"); Debug.Assert(mesh.faces.Count == 6, "face count"); foreach (Face f in mesh.faces) { Debug.Assert(f.vertcount == 4, "faces are quads"); } foreach (var v in mesh.vertices) { var uv = v.attributes["uv"] as FloatAttributeValue; Debug.Assert(Mathf.Abs(uv.data[0] - v.point.x) < epsilon && Mathf.Abs(uv.data[1] - v.point.z) < epsilon, "attribute interpolation: " + uv.data[0] + " == " + v.point.x + " && " + uv.data[1] + " == " + v.point.z); } Debug.Log("TestBMeshOperators TestSubdivideTris passed."); return(true); }
static bool Test1() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-0.5f, 0.0f, -Mathf.Sqrt(3) / 6)); Vertex v1 = mesh.AddVertex(new Vector3(0.5f, 0.0f, -Mathf.Sqrt(3) / 6)); Vertex v2 = mesh.AddVertex(new Vector3(0, 0.0f, Mathf.Sqrt(3) / 3)); Face f = mesh.AddFace(v0, v1, v2); Debug.Assert(mesh.vertices.Count == 3, "vert count"); Debug.Assert(mesh.loops.Count == 3, "loop count"); Debug.Assert(mesh.edges.Count == 3, "edge count"); Debug.Assert(mesh.faces.Count == 1, "face count"); Loop l = mesh.loops[0]; for (int i = 0; i < 3; ++i) { var v = mesh.vertices[i]; Debug.Assert(mesh.loops[i].face == f, "loop has face"); Debug.Assert(mesh.loops[i].edge != null, "loop has edge"); Debug.Assert(mesh.edges[i].loop != null, "edge has loop"); Debug.Assert(v.edge != null, "vertex has edge"); Debug.Assert(v.edge.vert1 == v || v.edge.vert2 == v, "vertex is in vertex edge"); Debug.Assert(l.next != l, "loop has next"); Debug.Assert(l.next.prev == l, "loop has consistent next"); Debug.Assert(l.radial_next.radial_prev == l, "loop has consistent radial next"); l = l.next; } Debug.Assert(l == mesh.loops[0], "loop loops"); Debug.Assert(mesh.FindEdge(v0, v1) != null, "edge between v0 and v1"); Debug.Assert(mesh.FindEdge(v0, v2) != null, "edge between v0 and v2"); Debug.Assert(mesh.FindEdge(v2, v1) != null, "edge between v2 and v1"); Debug.Log("TestBMesh #1 passed."); return(true); }
static bool Test3() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); Face f0 = mesh.AddFace(v0, v1, v2); Face f1 = mesh.AddFace(v2, v1, v3); Debug.Assert(mesh.vertices.Count == 4, "vert count"); Debug.Assert(mesh.loops.Count == 6, "loop count"); Debug.Assert(mesh.edges.Count == 5, "edge count"); Debug.Assert(mesh.faces.Count == 2, "face count"); Edge e0 = null; foreach (Edge e in mesh.edges) { if ((e.vert1 == v1 && e.vert2 == v2) || (e.vert1 == v2 && e.vert2 == v1)) { e0 = e; break; } } Debug.Assert(e0 != null, "found edge between v1 and v2"); mesh.RemoveEdge(e0); Debug.Assert(mesh.vertices.Count == 4, "vert count after removing edge"); Debug.Assert(mesh.loops.Count == 0, "loop count after removing edge"); Debug.Assert(mesh.edges.Count == 4, "edge count after removing edge"); Debug.Assert(mesh.faces.Count == 0, "face count after removing edge"); Debug.Log("TestBMesh #3 passed."); return(true); }
/** * 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(); } }
/////////////////////////////////////////////////////////////////////////// #region [Merge] /** * Merge a Unity Mesh into a BMesh. Can be used with an empty BMesh to * create a BMesh from a Unity Mesh * TODO: Add support for uvs etc. */ public static void Merge(BMesh mesh, Mesh unityMesh, bool flipFaces = false) { Vector3[] unityVertices = unityMesh.vertices; Vector2[] unityUvs = unityMesh.uv; Vector2[] unityUvs2 = unityMesh.uv2; Vector3[] unityNormals = unityMesh.normals; Color[] unityColors = unityMesh.colors; bool hasUvs = unityUvs != null && unityUvs.Length > 0; bool hasUvs2 = unityUvs2 != null && unityUvs2.Length > 0; bool hasNormals = unityNormals != null && unityNormals.Length > 0; bool hasColors = unityColors != null && unityColors.Length > 0; int[] unityTriangles = unityMesh.triangles; var verts = new Vertex[unityVertices.Length]; if (hasUvs) { mesh.AddVertexAttribute(new AttributeDefinition("uv", AttributeBaseType.Float, 2)); } if (hasUvs2) { mesh.AddVertexAttribute(new AttributeDefinition("uv2", AttributeBaseType.Float, 2)); } if (hasNormals) { mesh.AddVertexAttribute(new AttributeDefinition("normal", AttributeBaseType.Float, 3)); } if (hasColors) { mesh.AddVertexAttribute(new AttributeDefinition("color", AttributeBaseType.Float, 4)); } for (int i = 0; i < unityVertices.Length; ++i) { Vector3 p = unityVertices[i]; verts[i] = mesh.AddVertex(p); if (hasUvs) { verts[i].attributes["uv"].asFloat().FromVector2(unityUvs[i]); } if (hasUvs2) { verts[i].attributes["uv2"].asFloat().FromVector2(unityUvs2[i]); } if (hasNormals) { verts[i].attributes["normal"].asFloat().FromVector3(unityNormals[i]); } if (hasColors) { verts[i].attributes["color"].asFloat().FromColor(unityColors[i]); } } for (int i = 0; i < unityTriangles.Length / 3; ++i) { mesh.AddFace( verts[unityTriangles[3 * i + (flipFaces ? 1 : 0)]], verts[unityTriangles[3 * i + (flipFaces ? 0 : 1)]], verts[unityTriangles[3 * i + 2]] ); } }
static bool TestAttributes() { var mesh = new BMesh(); mesh.AddVertexAttribute(new AttributeDefinition("test", AttributeBaseType.Float, 3)); mesh.AddEdgeAttribute("edgetest", AttributeBaseType.Float, 2); mesh.AddFaceAttribute("facetest", AttributeBaseType.Int, 1); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); Face f0 = mesh.AddFace(v0, v1, v2); Face f1 = mesh.AddFace(v2, v1, v3); var otherAttr = new AttributeDefinition("other", AttributeBaseType.Int, 1); var def = otherAttr.defaultValue as IntAttributeValue; def.data[0] = 42; mesh.AddVertexAttribute(otherAttr); foreach (var v in mesh.vertices) { Debug.Assert(v.attributes.ContainsKey("test"), "vertex has test attribute"); var test = v.attributes["test"] as FloatAttributeValue; Debug.Assert(test != null, "vertex test attribute has float value"); var testAsInt = v.attributes["test"] as IntAttributeValue; Debug.Assert(testAsInt == null, "vertex test attribute has no int value"); Debug.Assert(test.data.Length == 3, "vertex test attribute has 3 dimensions"); Debug.Assert(test.data[0] == 0 && test.data[1] == 0 && test.data[2] == 0, "vertex test attribute has value (0, 0, 0)"); Debug.Assert(v.attributes.ContainsKey("other"), "vertex has other attribute"); var other = v.attributes["other"] as IntAttributeValue; Debug.Assert(other.data.Length == 1, "vertex other attribute has 1 dimension"); Debug.Assert(other.data[0] == 42, "vertex other attribute has value 42"); } foreach (var e in mesh.edges) { Debug.Assert(e.attributes.ContainsKey("edgetest"), "edge has test attribute"); var edgetest = e.attributes["edgetest"] as FloatAttributeValue; Debug.Assert(edgetest != null, "edge test attribute has float value"); var edgetestAsInt = e.attributes["edgetest"] as IntAttributeValue; Debug.Assert(edgetestAsInt == null, "edge test attribute has no int value"); Debug.Assert(edgetest.data.Length == 2, "edge test attribute has 2 dimensions"); Debug.Assert(edgetest.data[0] == 0 && edgetest.data[1] == 0, "edge test attribute has value (0, 0)"); } foreach (var f in mesh.faces) { Debug.Assert(f.attributes.ContainsKey("facetest"), "face has test attribute"); var facetest = f.attributes["facetest"] as IntAttributeValue; Debug.Assert(facetest != null, "face test attribute has int value"); var facetestAsFloat = f.attributes["facetest"] as FloatAttributeValue; Debug.Assert(facetestAsFloat == null, "face test attribute has no float value"); Debug.Assert(facetest.data.Length == 1, "face test attribute has 1 dimensions"); Debug.Assert(facetest.data[0] == 0, "face test attribute has value (0)"); } { var other1 = v1.attributes["other"] as IntAttributeValue; var other2 = v2.attributes["other"] as IntAttributeValue; other1.data[0] = 43; Debug.Assert(other2.data[0] == 42, "default vertex attribute values are independent"); } { var v4 = new Vertex(new Vector3(0, 0, 0)) { attributes = new Dictionary <string, AttributeValue> { ["other"] = new FloatAttributeValue { data = new float[] { 1, 2, 3 } } } }; Debug.Log("Test expects warning:"); Debug.Assert(mesh.AddVertex(v4) == v4, "add point is conservative"); Debug.Assert(v4.attributes.ContainsKey("test"), "new vertex has test attribute"); var test = v4.attributes["test"] as FloatAttributeValue; Debug.Assert(test.data[0] == 0 && test.data[1] == 0 && test.data[2] == 0, "new vertex test attribute has value (0, 0, 0)"); var other = v4.attributes["other"] as IntAttributeValue; Debug.Assert(other != null, "new vertex other attribute is an int"); Debug.Assert(other.data[0] == 42, "new vertex other attribute ignored non int provided value"); } Debug.Log("TestBMesh TestAttributes passed."); return(true); }
static bool Test2() { var mesh = new BMesh(); Vertex v0 = mesh.AddVertex(new Vector3(-1, 0, -1)); Vertex v1 = mesh.AddVertex(new Vector3(-1, 0, 1)); Vertex v2 = mesh.AddVertex(new Vector3(1, 0, 1)); Vertex v3 = mesh.AddVertex(new Vector3(1, 0, -1)); Face f = mesh.AddFace(v0, v1, v2, v3); Debug.Assert(mesh.vertices.Count == 4, "vert count"); Debug.Assert(mesh.loops.Count == 4, "loop count"); Debug.Assert(mesh.edges.Count == 4, "edge count"); Debug.Assert(mesh.faces.Count == 1, "face count"); // Edges Edge e0 = mesh.FindEdge(v0, v1); Edge e1 = mesh.FindEdge(v1, v2); Edge e2 = mesh.FindEdge(v2, v3); Edge e3 = mesh.FindEdge(v3, v0); Debug.Assert(e0 != null, "found edge v0->v1"); Debug.Assert(e1 != null, "found edge v1->v2"); Debug.Assert(e2 != null, "found edge v2->v3"); Debug.Assert(e3 != null, "found edge v3->v0"); Vector3 expected; expected = new Vector3(-1, 0, 0); Debug.Assert(Vector3.Distance(expected, e0.Center()) < epsilon, "edge 0 center"); expected = new Vector3(0, 0, 1); Debug.Assert(Vector3.Distance(expected, e1.Center()) < epsilon, "edge 1 center"); expected = new Vector3(1, 0, 0); Debug.Assert(Vector3.Distance(expected, e2.Center()) < epsilon, "edge 2 center"); expected = new Vector3(0, 0, -1); Debug.Assert(Vector3.Distance(expected, e3.Center()) < epsilon, "edge 3 center"); // face expected = new Vector3(0, 0, 0); Debug.Assert(Vector3.Distance(expected, f.Center()) < epsilon, "face center"); // Loop consistency v0.id = 0; v1.id = 1; v2.id = 2; v3.id = 3; Loop l = v0.edge.loop; Loop it = l; int prevId = it.prev.vert.id; int forward = (prevId + 1) % 4 == it.vert.id ? 1 : 0; do { Debug.Assert((forward == 1 && (prevId + 1) % 4 == it.vert.id) || (it.vert.id + 1) % 4 == prevId, "valid quad loop order"); prevId = it.vert.id; it = it.next; } while (it != l); for (int i = 0; i < 4; ++i) { var v = mesh.vertices[i]; Debug.Assert(mesh.loops[i].face == f); Debug.Assert(v.edge != null); Debug.Assert(v.edge.vert1 == v || v.edge.vert2 == v); } Debug.Assert(mesh.FindEdge(v0, v1) != null, "edge between v0 and v1"); mesh.RemoveEdge(mesh.edges[0]); Debug.Assert(mesh.vertices.Count == 4, "vert count after removing edge"); Debug.Assert(mesh.loops.Count == 0, "loop count after removing edge"); Debug.Assert(mesh.edges.Count == 3, "edge count after removing edge"); Debug.Assert(mesh.faces.Count == 0, "face count after removing edge"); Debug.Log("TestBMesh #2 passed."); return(true); }