Round Procedural Mesh Corners? C#

Good Afternoon, I have a script to build a procedural 2d square mesh but cannot figure out how to round the corners with a variable slider to increases roundness of the square mesh. Please see image and code bellow.

50888-image.png

Thanks Again for all your help :slight_smile:

Current Code:

 using UnityEngine;
 using UnityEditor;
 using System.Collections;
 
 public class Generate : MonoBehaviour
 {
 
  private Mesh s;

 public int RoundEdges; // Rounds all edges //
 public int RoundTopLeft; // Rounds Top Left Edge //
 public int RoundTopRight; // Rounds Top Right Edge //
 public int RoundBottomLeft; //Rounds Bottom Left Edge //
 public int RoundBottomRight; //Rounds Bottom Right Edge //
 
  private Vector3[] vertices = {
      new Vector2(0,0),
      new Vector2(0,1),
      new Vector2(1,1),
      new Vector2(1,0)
  };
 
  private int[] triangles = {
      0,1,2,
      2,3,0
  };   
 
  public void Create()
  {
      s = new Mesh();
      GetComponent<MeshFilter>().sharedMesh = s;
      s.name = "S";
  }
 
  void Start()
  {
      Create();
  }
 void Update(){
 // Round Edges //
 s.vertices = vertices;
 s.triangles = triangles;
}

}

I usually don’t create scripts for others here on UA since that’s not what this site is good for. @fafase and @_Gkxd gave you more than enough information to implement it yourself. I just want to make it clear that this is an exception. I though that i could use something like that for myself the other day…

So i’ve just written a rounded quad generator and made almost everything adjustable. You can specify:

  • the size of the quad
  • If it’s double sided or single sided. Single sided mesh will face the “-z-axis” so it’s compatible with 2d.
  • If the script should generate UV coordinates.
  • How many vertices should be generated for one corner [2,16000]. If 2 is used you get a “beveled” corner.
  • the base radius of all corners. (*1)
  • each radius independently from the others.(*1)
  • if the mesh should be updated each frame (AutoUpdate). Keep in mind that it’s quite demanding to recreate the mesh each frame, so you might want to turn it off and update the mesh when you changed something.

(*1) The base radius is added to the independent one for each edge. You can also specify negative values. The resulting value is clamped between [0,1]

//RoundedQuadMesh.cs
using UnityEngine;
using System.Collections;

public class RoundedQuadMesh : MonoBehaviour
{
    public float RoundEdges = 0.5f;
    public float RoundTopLeft = 0.0f;
    public float RoundTopRight = 0.0f;
    public float RoundBottomLeft = 0.0f;
    public float RoundBottomRight = 0.0f;
    public float Size = 1f;
    public int CornerVertexCount = 8;
    public bool CreateUV = true;
    public bool DoubleSided = false;
    public bool AutoUpdate = true;

    private MeshFilter m_MeshFilter;
    private Mesh m_Mesh;
    private Vector3[] m_Vertices;
    private Vector3[] m_Normals;
    private Vector2[] m_UV;
    private int[] m_Triangles;

    void Start()
    {
        m_MeshFilter = GetComponent<MeshFilter>();
        if (m_MeshFilter == null)
            m_MeshFilter = gameObject.AddComponent<MeshFilter>();
        if (GetComponent<MeshRenderer>() == null)
            gameObject.AddComponent<MeshRenderer>();
        m_Mesh = new Mesh();
        m_MeshFilter.sharedMesh = m_Mesh;
        UpdateMesh();
    }

    public Mesh UpdateMesh()
    {
        if (CornerVertexCount < 2)
            CornerVertexCount = 2;
        int sides = DoubleSided ? 2 : 1;
        int vCount = CornerVertexCount * 4 * sides + sides;
        int triCount = (CornerVertexCount * 4) * sides;
        if (m_Vertices == null || m_Vertices.Length != vCount)
        {
            m_Vertices = new Vector3[vCount];
            m_Normals = new Vector3[vCount];
        }
        if (m_Triangles == null || m_Triangles.Length != triCount * 3)
            m_Triangles = new int[triCount * 3];
        if (CreateUV && (m_UV == null || m_UV.Length != vCount))
        {
            m_UV = new Vector2[vCount];
        }
        float f = 1f / (CornerVertexCount - 1);
        m_Vertices[0] = Vector3.zero;
        int count = CornerVertexCount * 4;
        if (CreateUV)
        {
            m_UV[0] = Vector2.one * 0.5f;
            if (DoubleSided)
                m_UV[count + 1] = m_UV[0];
        }

        for (int i = 0; i < CornerVertexCount; i++)
        {
            float s = Mathf.Sin((float)i * Mathf.PI * 0.5f * f);
            float c = Mathf.Cos((float)i * Mathf.PI * 0.5f * f);
            float tl = Mathf.Clamp01(RoundTopLeft + RoundEdges);
            float tr = Mathf.Clamp01(RoundTopRight + RoundEdges);
            float bl = Mathf.Clamp01(RoundBottomLeft + RoundEdges);
            float br = Mathf.Clamp01(RoundBottomRight + RoundEdges);
            Vector2 v1 = new Vector3(-1f + tl - c * tl, 1 - tl + s * tl);
            Vector2 v2 = new Vector3(1f - tr + s * tr, 1f - tr + c * tr);
            Vector2 v3 = new Vector3(1f - br + c * br, -1f + br - s * br);
            Vector2 v4 = new Vector3(-1f + bl - s * bl, -1f + bl - c * bl);

            m_Vertices[1 + i] = v1 * Size;
            m_Vertices[1 + CornerVertexCount + i] = v2 * Size;
            m_Vertices[1 + CornerVertexCount * 2 + i] = v3 * Size;
            m_Vertices[1 + CornerVertexCount * 3 + i] = v4 * Size;
            if (CreateUV)
            {
                m_UV[1 + i] = v1 * 0.5f + Vector2.one * 0.5f;
                m_UV[1 + CornerVertexCount * 1 + i] = v2 * 0.5f + Vector2.one * 0.5f;
                m_UV[1 + CornerVertexCount * 2 + i] = v3 * 0.5f + Vector2.one * 0.5f;
                m_UV[1 + CornerVertexCount * 3 + i] = v4 * 0.5f + Vector2.one * 0.5f;
            }
            if (DoubleSided)
            {
                // The backside vertices are in reverse order
                m_Vertices[1 + CornerVertexCount * 7 + CornerVertexCount - i] = v1 * Size;
                m_Vertices[1 + CornerVertexCount * 6 + CornerVertexCount - i] = v2 * Size;
                m_Vertices[1 + CornerVertexCount * 5 + CornerVertexCount - i] = v3 * Size;
                m_Vertices[1 + CornerVertexCount * 4 + CornerVertexCount - i] = v4 * Size;
                if (CreateUV)
                {
                    m_UV[1 + CornerVertexCount * 7 + CornerVertexCount - i] = v1 * 0.5f + Vector2.one * 0.5f;
                    m_UV[1 + CornerVertexCount * 6 + CornerVertexCount - i] = v2 * 0.5f + Vector2.one * 0.5f;
                    m_UV[1 + CornerVertexCount * 5 + CornerVertexCount - i] = v3 * 0.5f + Vector2.one * 0.5f;
                    m_UV[1 + CornerVertexCount * 4 + CornerVertexCount - i] = v4 * 0.5f + Vector2.one * 0.5f;
                }
            }
        }
        for (int i = 0; i < count + 1; i++)
        {
            m_Normals[i] = -Vector3.forward;
            if (DoubleSided)
                m_Normals[count + 1 + i] = Vector3.forward;
        }
        for (int i = 0; i < count; i++)
        {
            m_Triangles[i * 3] = 0;
            m_Triangles[i * 3 + 1] = i + 1;
            m_Triangles[i * 3 + 2] = i + 2;
            if (DoubleSided)
            {
                m_Triangles[(count + i) * 3] = count + 1;
                m_Triangles[(count + i) * 3 + 1] = count + 1 + i + 1;
                m_Triangles[(count + i) * 3 + 2] = count + 1 + i + 2;
            }
        }
        m_Triangles[count * 3 - 1] = 1;
        if (DoubleSided)
            m_Triangles[m_Triangles.Length - 1] = count + 1 + 1;
        m_Mesh.Clear();
        m_Mesh.vertices = m_Vertices;
        m_Mesh.normals = m_Normals;
        if (CreateUV)
            m_Mesh.uv = m_UV;
        m_Mesh.triangles = m_Triangles;
        return m_Mesh;
    }

    void Update()
    {
        if (AutoUpdate)
            UpdateMesh();
    }
}

edit
I created a modified version of the script (which you can find on my dropbox here)and added those features:

  • You can specify a “Rect” in local space to define the quad. So it isn’t limited to squares anymore*
  • The rounding feature can be toggled between percentage mode (the old one) and absolute mode. Note: In absolute mode it might still make the radius smaller when the over-all width (or height) is exceeded. However if the rect is large enough the radius of all edges should be constant even when you change the width / height. In percentage mode it always uses the smaller edge as reference. So the corners don’t get a streched circle. They will always be a quarter circle.
  • “Size” has been replaced with “Scale” which simply scales the endresult around the origin just like Unity’s scale would like but it scales the actual vertices in localspace.
  • Added a “FlipBackFaceUV” flag to flip the uv horizontally (u = 1f-u)

edit
If someone’s interested in how it looks like, here’s a Unity webplayer build

Beziers or Catmull-Rom curve.

Here below is Catmull-Rom equation from Centripetal Catmull–Rom spline - Wikipedia

public static Vector2 CatmullRomTangent(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t) {
		return 0.5f * ((-p1 + p3) + 2f * (2f * p1 - 5f * p2 + 4f * p3 - p4) * t + 3f * (-p1 + 3f * p2 - 3f * p3 + p4) * Mathf.Pow(t, 2f));
}

You can control the t value to make it smoother when the slider moves.

EDIT: So you have point on your object, for instance a triangle and you want to smooth the corners.

Your triangle has vertex array {A,B,C}. You start iterating with A and in this case you can use C as previous. If the curve is not closed then you use Vector2.zero. And you start iterating the ratio t. So consider you want 5 points between, you have t = 0, 0.25, 0.5, 0.75 ,1.0.

so you get:

 for(int t = 0; t <= 1.0f; t = t + precision)
    Vector2 position = CatmullRomTangent(C, A, B, C, t);

Then you move the content so that B - C is the middle part and iterate t again.

Your job is to get the position value to place a new vertex in the scene. precision is the value taken from the slider and the smaller it is, the more iteration you get and the more expensive it gets.