Unity Mesh Modification

As part of the technical preview for a project I’m working on I have ran into an interesting problem that I’m trying to solve. In Unity when you use the OnCollisionEnter/OnCollisionExit collision handling and need the information about the mesh it is colliding with.

Modifying the mesh requires knowledge of the triangles that you are interacting with; however OnCollisionEnter doesn’t give you this information, you need a RayCastHit to access this information. One way to get the RayCastHit is to perform a Physics.Raycast call based on the collision contacts. Once you have the RayCastHit you can use triangleIndex on that object to get the required information including the vertex information.

Going from the OnCollisionEnter collision contacts to a RayCastHit that gives you the correct information isn’t as simple as it should be. You have to get an offset point that is just behind the collision point, and then do the Raycast from there otherwise it detect the triangles further away and misses the one that it should be detecting. Below is the code that I used to get this all working.

private void OnCollisionEnter(Collision collision) {
    GameObject colGo = collision.collider.gameObject;
    points = collision.contacts;
    if (points.Length > 0) {
        for (int i = 0; i < points.Length; i++) {
            Vector3 offsetPoint = points[i].point - (points[i].normal + (Vector3.down * 1.1f));
            Debug.DrawRay(offsetPoint, points[i].normal, Color.blue);
            Debug.DrawRay(offsetPoint, -points[i].normal, Color.yellow);
            Debug.LogFormat("{0}-{1}=>{2}", points[i].point, points[i].normal, offsetPoint);
            if (Physics.Raycast(offsetPoint, -points[i].normal, out RaycastHit hit, Mathf.Infinity)) {
                Debug.LogFormat("{0}||{1}", points[i].point, hit.triangleIndex);
                if (hit.triangleIndex != -1) {
                    Mesh mesh = hit.collider.gameObject.GetComponent<MeshFilter>().sharedMesh;
                    Vector3[] verts = mesh.vertices;
                    int[] triangles = mesh.triangles;
                    Vector3 _a = hit.transform.TransformPoint(verts[triangles[hit.triangleIndex * 3]]);
                    Vector3 _b = hit.transform.TransformPoint(verts[triangles[hit.triangleIndex * 3 + 1]]);
                    Vector3 _c = hit.transform.TransformPoint(verts[triangles[hit.triangleIndex * 3 + 2]]);
                    Debug.LogFormat("{0}|{1}|{2}", _a, _b, _c);
                    Debug.DrawLine(_a, _b);
                    Debug.DrawLine(_b, _c);
                    Debug.DrawLine(_c, _a);
                }
            }
        }
    }
}

Unity Custom Editor

This week I have been working on a Unity custom editor, with the goal of making meshes easier to work with.

It made sense to show this editor for the MeshFilter type, since that’s the script unity uses to store the Mesh object on. Anytime the MeshFilter selected in the editor the inspector shows the custom editor inspector.

The plan for the inspector was to show just the details of the Mesh; such as the number of vertices and the number of triangles. This could then be expanded to show the values of the vertices and triangles, and lastly allow them to be live editable.

The first part of the custom inspector; showing the details; was very simple, code shown below.

for (int idx = 0; idx < mesh.vertices.Length; idx++)
{
    string str = string.Format("{0} {1}", idx, mesh.vertices[idx].ToString());
    EditorGUILayout.LabelField(str);
}
int count = 0;
for (int idx = 0; idx < mesh.triangles.Length / 3; idx++)
{
    string str = string.Format(
        "{0}, {1}, {2}",
        mesh.triangles[count++],
        mesh.triangles[count++],
        mesh.triangles[count++]
    );
    EditorGUILayout.LabelField(str);
}

Showing the values of the vertices and triangles was simple. After viewing the output of this I came to the realization that most meshes have many duplicate vertices; hinting to me that an optimize functionality would come in handy. More on this optimization later. Below is the code used to display the mesh details.

public (Vector3[], int[]) Optimize(Mesh mesh)
{
    List<Vector3> verts = new List<Vector3>();
    Dictionary<int, int> nLocation = new Dictionary<int, int>();
    List<int> tris = new List<int>();
    for (int vIndex = 0; vIndex < mesh.vertices.Length; vIndex++)
    {
        Vector3 vert = mesh.vertices[vIndex];
        int idx = verts.IndexOf(vert);
        if (idx == -1)
        {
            idx = verts.Count;
            verts.Add(vert);
        }
        nLocation[vIndex] = idx;
    }
    for (int tIndex = 0; tIndex < mesh.triangles.Length; tIndex++)
    {
        int nLoc = nLocation[mesh.triangles[tIndex]];
        Debug.Log(nLoc);
        tris.Add(nLocation[mesh.triangles[tIndex]]);
    }
    Mesh m2 = new Mesh();
    m2.triangles = new int[0];
    m2.vertices = verts.ToArray();
    m2.triangles = tris.ToArray();
    return (verts.ToArray(), tris.ToArray());
}

After running this code on the Cube mesh the results contain much less vertices, while retaining the original shape.

Solution #1 Unity Mesh Walls

The solution I have chosen is as follows. Use bezier curve that has 2 control points, these are each end of the line. Then set the rotation based on the tangent at the midpoint. Code shown below.

private static Vector3 CalculateTangent(float t, Vector3 p0, Vector3 p1)
{
    float a = 1 - t;
    float b = a * 6 * t;
    a = a * a * 3;
    float c = t * t * 3;
    return (-a * p0) + (c * p1);
}
public Mesh CalculateMesh()
{
    Mesh mesh = new Mesh();
    List<Vector3> verts = new List<Vector3>();
    Quaternion quaternion = Quaternion.LookRotation(CalculateTangent(0.5f, startPosition, endPosition));
    verts.Add( (quaternion * new Vector3(-widthOffset, 0.0f, 0.0f)) + startPosition );   // 0
    verts.Add( (quaternion * new Vector3(widthOffset, 0.0f, 0.0f)) + startPosition );    // 1
    verts.Add( (quaternion * new Vector3(-widthOffset, height, 0.0f)) + startPosition ); // 2
    verts.Add( (quaternion * new Vector3(widthOffset, height, 0.0f)) + startPosition );  // 3
    verts.Add( (quaternion * new Vector3(-widthOffset, 0.0f, 0.0f)) + endPosition );     // 4
    verts.Add( (quaternion * new Vector3(widthOffset, 0.0f, 0.0f)) + endPosition );      // 5
    verts.Add( (quaternion * new Vector3(-widthOffset, height, 0.0f)) + endPosition );   // 6
    verts.Add( (quaternion * new Vector3(widthOffset, height, 0.0f)) + endPosition );    // 7
    Face[] faces = new Face[] {
        new Face(0, 1, 2, 3),
        new Face(0, 1, 4, 5),
        new Face(0, 2, 4, 6),
        new Face(1, 3, 5, 7),
        new Face(2, 3, 6, 7),
        new Face(4, 5, 6, 7)
    };
    List<int> tris = new List<int>();
    for (int i = 0; i < faces.Length; i++)
    {
        tris.AddRange(faces[i].Tris());
    }
    mesh.vertices = verts.ToArray();
    mesh.triangles = tris.ToArray();
    return mesh;
}

Problem #1 Unity Mesh Walls

This is a problem I have encountered while working on my personal side project, a simulation game. Unity mesh allows developers to dynamically create meshes at runtime. Meshes take an array of Vector3s and an array of Integers to create the mesh. Vector3 is a data type that stores the X, Y, and Z positions as floating point numbers.

Creating an array of Vector3s, which is the vertices, and the array of integers; signifying the triangles; allows the developer to make any shape they would like. The array of integers which is grouped into threes, tells the mesh system which three vertices, by index from the array of vertices, make up the triangle. All triangles are rendered as one sided triangles, developers can change which side is rendered by swapping the first and last indexes of the triangle. Example 1,2,3 should be come 3,2,1 to reverse it.

Wall generation will utilize a starting and ending point, passed in as Vector3s. They also have an extrude height and an extrude width. This will allow a lot of customization in the long run. Below is an image showing the wall generation tool with two selected points that are 3 units apart in the X direction.

However when you make the same distance wall in the Z direction it looks more like the image below.

Broken Dynamically Generated Wall

The problem is when the walls are generated in the Z direction their extrude outward from the input points isn’t calculated correctly and makes the wall flat. Below is the code used to add the vertices to the list of vertices.

verts.Add(startPosition + new Vector3(0.0f, 0.0f, -widthOffset)); // 0
verts.Add(startPosition + new Vector3(0.0f, 0.0f, widthOffset));  // 1
verts.Add(startPosition + new Vector3(0.0f, height, -widthOffset)); // 2
verts.Add(startPosition + new Vector3(0.0f, height, widthOffset));  // 3
verts.Add(endPosition + new Vector3(0.0f, 0.0f, -widthOffset)); // 4
verts.Add(endPosition + new Vector3(0.0f, 0.0f, widthOffset));  // 5
verts.Add(endPosition + new Vector3(0.0f, height, -widthOffset));// 6
verts.Add(endPosition + new Vector3(0.0f, height, widthOffset)); // 7

Potential Solutions

Solution #1: Create the object using the distance between the start position and end position in the X(or Z) direction and then rotate the object towards the end point. This might work if you use the origin as the start position and then set transform.position on the object to move it to the correct position.

Solution #2: Use mathematic formula derived from the formula to calculate the tangent of a bezier curve to calculate the rotation needed at the start and end points.

Solution #3: Use bezier curve that has 4 points and set the two control points to the midpoint of the line. I have already implemented a 4 point bezier wall generation system, wouldn’t be too hard to convert. Four point bezier curve is shown below in wireframe mode.

Polygon Generation in Unity

Dynamic generation of polygonal 2d meshes is a useful stepping stone to more dynamic shapes. Unity Mesh object can be broken down into two major components, an array of Vector3s and an array of integers. The simplest polygons have equal angles and side lengths, this is an easy calculation to do; to calculate the X and Y values we use Cosine and Sine functions.

float angle = Mathf.Deg2Rad * ( 360 / sides );
Vector3[] verts = new Vector3[sides];
for (int i = 0; i < verts.Length; i++)
{
    float h = angle * i;
    float x = Mathf.Cos(h) * distance;
    float y = Mathf.Sin(h) * distance;
    verts[i] = new Vector3(x, y, 0.0f);
 }

The next step of making a mesh dynamically is to determine the triangles that the face is made up of. We first figure out how many triangles we require, this can be done by taking the number of vertices and subtract 2. Now we can walk thru each triangle and determine the index of each vertex. Making the simple assumption that every triangle of the face is going to start at the first vertex; we then have to determine the two other vertices. Once we determine the vertex indices of the individual triangles we then have to add them to the mesh triangles indices list. Before we can add them to that array we must order them so that the faces are facing the correct direction.

List<int> triangles = new List<int>();
int triangleCount = verts.Length - 2;
for (int i = 0; i < triangleCount; i++)
{
    int a = 0;
    int b = 1 + i;
    int c = 2 + i;
    switch (faceDirection)
    {
        case FaceDirection.FRONT:
            triangles.AddRange(new int[] { c, b, a });
            break;
        case FaceDirection.BACK:
            triangles.AddRange(new int[] { a, b, c });
            break;
        case FaceDirection.BOTH:
            triangles.AddRange(new int[] { c, b, a });
            triangles.AddRange(new int[] { a, b, c });
            break;
    }
}

Now that we have both the components we need, time to apply them to the mesh object.

Mesh mesh = new Mesh();
mesh.vertices = verts;
mesh.triangles = triangles.ToArray();

Once you have the mesh object, you will have to set it to the MeshFilter component on a game object. This can be used in both the Start and Update methods.

Sometimes things break

If you randomly start to see 500+ errors on your sites, changes are there’s a bug you didn’t know about. Today I discovered one. One of my projects was returning a 502 error, I reviewed the logs. The logs basically told me that my database find was using too much ram. After some googling I determined I had to use an index on one of the models inside of my project. Very simple fix and works correctly now.

Make sure you check the basic functionality of your projects every so often. You will find sometimes it will alert you to problems you didn’t know about.

Sometimes you’ll have to change your application due to how people use it.

Initial brainstorm phase can only get you so far, the real test of your project is letting general people access it without you looking over their shoulder. Sometimes you will find that your assumptions are wrong, or that you need to add extra fields to the form. Say for when people decide to apply to use an TestFlight build, and they don’t have an iPhone. Don’t get mad at your users when you have to change your project to handle how they are actually going to use it. Don’t forget to test your projects with real people.