Side trip: How to draw a line in Unity

I’ve been secretly working on a another game project (codename ”Grow Some Ball”). For that game I implemented a line drawing script since the Unity’s own Line Renderer was too restricted. The script draws a line during mouse dragging – between the start click point and the current mouse position. The line is a quad drawn with two adjacent triangles.

Untitled
Mouse drag line drawn with red material (thickness increased to show the quad)

The code is quite straightforward and I though it might be useful to some of your starting with Unity (and mesh generation). It simply transforms the mouse drag screen coordinates to world coordinates (with z -1.0) and calculates the positions of the four vertices that form the line quad. So here it is!

using UnityEngine;

public class LineDrawer : MonoBehaviour
{
    public Material lineMaterial;
    public float lineZ = -1.0f;
    public float lineThickness;

    bool dragOn = false;

    GameObject lineGO;
    Mesh lineMesh;
    Vector3[] lineNormals = new Vector3[4];
    Vector3[] lineVertices = new Vector3[4];
    int[] lineTriangles = new int[6];

    Vector3 dragStartPosition;
    Vector3 mouseCurrentPosition;

    void Start ()
    {
        lineGO = new GameObject("DragLine");
        MeshRenderer renderer =
            lineGO.AddComponent<MeshRenderer>();
        renderer.shadowCastingMode = 
            UnityEngine.Rendering.ShadowCastingMode.Off;
        renderer.material = lineMaterial;

        lineMesh = new Mesh();
        MeshFilter meshFilter = lineGO.AddComponent<MeshFilter>();
        meshFilter.mesh = lineMesh;

        lineTriangles[0] = 0;
        lineTriangles[1] = 1;
        lineTriangles[2] = 2;
        lineTriangles[3] = 0;
        lineTriangles[4] = 2;
        lineTriangles[5] = 3;
        lineNormals[0] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[1] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[2] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[3] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineVertices[0] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[1] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[2] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[3] = new Vector3(0.0f, 0.0f, lineZ);
    }

    void DrawDragLine()
    {
        Debug.Assert(dragOn);

        if (dragStartPosition == mouseCurrentPosition) return;

        Vector3 lineStart;
        Vector3 lineEnd;

        if (dragStartPosition.x > mouseCurrentPosition.x)
        {
            lineStart = mouseCurrentPosition;
            lineEnd = dragStartPosition;
        }
        else
        {
            lineStart = dragStartPosition;
            lineEnd = mouseCurrentPosition;
        }

        float angle = Mathf.Atan2(
            lineEnd.y - lineStart.y, lineEnd.x - lineStart.x);
        float angle2 = Mathf.PI / 2.0f - angle;
        float dx = lineThickness * Mathf.Cos(angle2);
        float dy = lineThickness * Mathf.Sin(angle2);

        lineVertices[0].x = lineStart.x + dx;
        lineVertices[0].y = lineStart.y - dy;

        lineVertices[1].x = lineStart.x - dx;
        lineVertices[1].y = lineStart.y + dy;

        lineVertices[2].x = lineEnd.x - dx;
        lineVertices[2].y = lineEnd.y + dy;

        lineVertices[3].x = lineEnd.x + dx;
        lineVertices[3].y = lineEnd.y - dy;

        lineMesh.vertices = lineVertices;
        lineMesh.triangles = lineTriangles;
        lineMesh.normals = lineNormals;
    }

    void Update ()
    {
        if (!dragOn)
        {
            if (Input.GetMouseButtonDown(0))
            {
                dragStartPosition = Camera.main.ScreenToWorldPoint(
                    Input.mousePosition);
                dragOn = true;
            }
        }
        else
        {
            if (Input.GetMouseButton(0))
            {
                mouseCurrentPosition =
                    Camera.main.ScreenToWorldPoint(
                    Input.mousePosition);
                DrawDragLine();
            }
            else
            {
                if (Input.GetMouseButtonUp(0))
                {
                    lineMesh.Clear();
                    dragOn = false;
                }
            }
        }
    }
}

Sorry for the lack of syntax highlighting 🙁 If your want to test the script create a new 2D project in Unity, create an empty GameObject to the scene and drag the script to it (so it becomes a Component). Assign a material to the script in the Inspector and select the desired line thickness. Have fun!

Joining adjacent triangles

Previously I wrote about useless triangles that are created when a mesh is split in two. By useless I mean they are adjacent to each other and on the same plane (plus have the same normal vectors). So why not join them? In the following I present two simple rules that can be used to determine if three triangles (created by splitting a mesh by plane) can be presented with two triangles.

From polygon to triangles

We need to reduce the number of triangles in a mesh. A quad (polygon with four vertices) can be represented with two triangles. So if we find quads that consist of more than two triangles we optimize!

BLOGO
A quad can be presented with two triangles, a polygon with five vertices needs more.

When looking for optimization candidates we only have to check the triangles that have one or two vertices on the splitting plane. We don’t want to accidentally ”optimize” other parts of the model that should be unaffected by the splitting 🙂 Also, if a triangle has one or all vertices on the splitting plane it is not interesting as it is not split.

A quad with three triangles, gross!!

If we take a triangle that has one or two vertices on a plane we have one of the following ”neighborhood situations”:

optimointi - viereisten kolmioiden yhdistäminen
The light green triangle has one or two vertices on the splitting plane (yellow line). All the triangles are on the same plane.

Below the triangles is the number of vertices in a polygon created by the intersecting triangle (light green) and its neighbors. If n = 4 we have a quad and we can represent that with two triangles. If n = 3 the situation is even better – we can represent all three triangles with a single triangle. Cool! If n > 4 we cannot join the triangles since impossibru!

Thats it! In one of the next articles I will show what kind of impact these two simple optimizations can actually have when implemented (in Unity).