Esempio n. 1
0
    ///////////////////////////////////////////////////////////////////////////
    #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);
    }
Esempio n. 2
0
    /**
     * 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;
        }
    }
Esempio n. 3
0
    ///////////////////////////////////////////////////////////////////////////
    #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();
        }
    }