Dynamically generated trail mesh

Hello,

As the TrailRenderer did not suit my needs due to billboarding of the particles I wrote this Javascript that generates a trail mesh along a game object’s x-axis. The script does what I wanted it to, so I thought I’ll share it with you

How to use:

Create an empty game object, attach a MeshFilter and a MeshRenderer component plus this script.
The setting “Trail Width” is pretty self explanatory. The setting “Trail Length” is the maximum length of the trail in quad segments, where each segment consists of two triangles.
The “Generate” checkbox can be used to switch the trail on and off from an other script. It will gradually retract from its end when switched off.
The trail will stretch an assigned texture across its entire length.

I have put the code in a FixedUpdate function to generate the segments at fixed intervals, but you can rename the function to change that or to call the mesh generation from an other script.

This was written for Unity3, to use it in Unity iPhone 1.7 just remove the “# pragma implicit” and “# pragma downcast” at the top.

Disclaimer:
Please note that I am not a programmer, so there is definitely room for improvement. I also commented out the code for generating normals as I think it is flawed and I don’t need normals for the material I’m using. Maybe someone with a better understanding of dynamic mesh creation wants to take a look at it? Also, if you make improvements to this code I’d love to hear…

Enough of the talking, here is the code:

# pragma strict
# pragma implicit
# pragma downcast

@script RequireComponent (MeshFilter)
@script RequireComponent (MeshRenderer)

var trailWidth : float;
var trailLength : int;
var generate = false;

private var mesh : Mesh;

private var vertex0 = Vector3(trailWidth / 2, 0, 0);
private var vertex1 = Vector3(-trailWidth / 2, 0, 0);
private var vertices : Array;
private var verticesWorld : Vector3[];
private var uvs : Vector2[];
private var triangles : Array;
private var normals : Vector3[];
private var vector1 : Vector3;
private var vector2 : Vector3;

function Start () {
	mesh = (GetComponent(MeshFilter) as MeshFilter).mesh;
	vertices = new Array();
	verticesWorld = new Array();
}

function FixedUpdate () {

	// clear old mesh before calculating a new one
	mesh.Clear();
	
	if (generate) {
		// set local position of the first 2 vertices
		vertex0 = Vector3(trailWidth / 2, 0, 0);
		vertex1 = Vector3(-trailWidth / 2, 0, 0);
	
		// add these vertices at the top of the vertex array
		vertices.Unshift(vertex0, vertex1);
	
		// if max length has been reached discard the last two vertices
		if (vertices.length > trailLength * 2 + 2) {
			vertices.Pop();
			vertices.Pop();
		}
	} else {
		// if trail has been disabled and there are still vertices discard the last two vertices
		if (vertices.length > 0) {
			vertices.Pop();
			vertices.Pop();
		}
	}

	// if we have at least 4 vertices
	if (vertices.length >= 4) {

		// read saved world positions shifted by 2 verts as local positions
		for (var i = 0; i < vertices.length - 2; i++) {
			vertices[i+2] = transform.InverseTransformPoint(verticesWorld[i]);
		}
		
		// add vertices to mesh
		mesh.vertices = vertices;
	}
	
	// save world positions of all vertices for reuse in next cycle
	verticesWorld = new Vector3[vertices.length];
	
	for (i = 0; i < vertices.length; i++) {
		verticesWorld[i] = transform.TransformPoint(vertices[i]);
	}
	
	
	
	if (vertices.length >= 4) {
	
		// calculate uvs
	
		uvs = new Vector2[vertices.length];
		
		uvs[0] = Vector2(0,0);
		
		for (i = 1; i < vertices.length; i++) {
			if (i%2) {
				uvs[i] = Vector2(0, i / (vertices.length - 2));
			} else {
				uvs[i] = Vector2(1, (i - 1) / (vertices.length - 2));
			}
		}
		
		mesh.uv = uvs;
		
		// calculate triangles
		
		triangles = new Array();
		
		// first triangle
		triangles.Add(0, 2, 1);
		
		// subsequent triangles
		for (i = 1; i < vertices.length - 2; i++) {
			if (i%2) {
				triangles.Add(i, i + 1, i + 2);
			} else {
				triangles.Add(i, i + 2, i + 1);
			}
		}
		
		mesh.triangles = triangles;
		
		// calculate normals
		/*
		normals = new Vector3[vertices.length];
		
		// normal for vertex 0
		vector1 = verticesWorld[1] - verticesWorld[0];
		vector2 = verticesWorld[2] - verticesWorld[0];
		
		if (Vector3.Angle(vector1, vector2) < 180) {
			normals[0] = Vector3.Cross(vector1, vector2);
			normals[0] = transform.InverseTransformDirection(normals[0].normalized);
		} else {
			normals[0] = Vector3.Cross(vector2, vector1);
			normals[0] = transform.InverseTransformDirection(normals[0].normalized);
		}
		
		// normals for subsequent vertices
		for (i = 1; i < vertices.length - 1; i++) {
			vector1 = verticesWorld[i-1] - verticesWorld[i];
			vector2 = verticesWorld[i+1] - verticesWorld[i];
			
			if (Vector3.Angle(vector1, vector2) < 180) {
				normals[i] = Vector3.Cross(vector1, vector2);
				normals[i] = transform.InverseTransformDirection(normals[i].normalized);
			} else {
				normals[i] = Vector3.Cross(vector2, vector1);
				normals[i] = transform.InverseTransformDirection(normals[i].normalized);
			}
		}
		
		// normal for last vertex
		vector1 = verticesWorld[vertices.length - 2] - verticesWorld[vertices.length - 1];
		vector2 = verticesWorld[vertices.length - 3] - verticesWorld[vertices.length - 1];
		
		if (Vector3.Angle(vector1, vector2) < 180) {
			normals[vertices.length - 1] = Vector3.Cross(vector1, vector2);
			normals[vertices.length - 1] = transform.InverseTransformDirection(normals[vertices.length - 1].normalized);
		} else {
			normals[vertices.length - 1] = Vector3.Cross(vector2, vector1);
			normals[vertices.length - 1] = transform.InverseTransformDirection(normals[vertices.length - 1].normalized);
		}
		
		mesh.normals = normals;
		*/
	}
	
}
2 Likes

Ten years later and this is the only free solution for a nice sword trail renderer around! Thanks a ton dude!
For anyone else who’s looking, I’ve converted the script to C# and slightly optimized it (I think).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class MeshTrail : MonoBehaviour
{
    //#pragma strict
    //#pragma implicit
    //#pragma downcast

    public float trailWidth;
    public float trailLength;
    public bool generate = false;
    public bool devTest = false;

    private Mesh mesh;
    private Vector3 vertex0;
    private Vector3 vertex1;
    private List<Vector3> vertices;
    private List<Vector3> verticesWorld;
    private Vector2[] uvs;
    private int[] triangles;

    // private List<Vector3> normals;
    // private Vector3 vector1;
    // private Vector3 vector2;
    void Start()
    {
        vertex0 = new Vector3(trailWidth / 2, 0, 0);
        vertex1 = new Vector3(-trailWidth / 2, 0, 0);
        mesh = GetComponent<MeshFilter>().mesh;
        vertices = new List<Vector3>();
        verticesWorld = new List<Vector3>();
    }

    void FixedUpdate()
    {
        // clear old mesh before calculating a new one
        mesh.Clear();

        if (generate)
        {
            // set local position of the first 2 vertices
            vertex0 = new Vector3(trailWidth / 2, 0, 0);
            vertex1 = new Vector3(-trailWidth / 2, 0, 0);

            // add these vertices at the top of the vertex array
            vertices.Insert(0, vertex0); // Uhhhh
            vertices.Insert(0, vertex1); // Uhhhh

            // if max length has been reached discard the last two vertices
            if (vertices.Count > trailLength * 2 + 2)
            {
                vertices.RemoveAt(vertices.Count - 1);
                vertices.RemoveAt(vertices.Count - 1);
            }
        }
        else
        {
            // if trail has been disabled and there are still vertices discard the last two vertices
            if (vertices.Count > 0)
            {
                vertices.RemoveAt(vertices.Count - 1);
                vertices.RemoveAt(vertices.Count - 1);
            }
        }

        int vertexCount = vertices.Count;

        // if we have at least 4 vertices
        if (vertexCount >= 4)
        {

            // read saved world positions shifted by 2 verts as local positions
            for (int i = 0; i < vertexCount - 2; i++)
            {
                vertices[i + 2] = transform.InverseTransformPoint(verticesWorld[i]);
            }

            // add vertices to mesh
            mesh.vertices = vertices.ToArray();
        }

        // save world positions of all vertices for reuse in next cycle
        verticesWorld = new List<Vector3>();

        for (int i = 0; i < vertexCount; i++)
        {
            verticesWorld.Add(transform.TransformPoint(vertices[i]));
        }

        if (vertexCount >= 4)
        {
            uvs = new Vector2[vertexCount];

            float vertexCountFloat = (float)vertexCount;
            for (int i = 1; i < vertexCount; i++)
            {
                if (i % 2 == 0)
                    uvs[i] = new Vector2(i / (vertexCountFloat - 2), 0);
                else
                    uvs[i] = new Vector2((i - 1) / (vertexCountFloat - 2), 1);
            }

            mesh.uv = uvs;

            // calculate triangles
            triangles = new int[(vertexCount - 2) * 3];

            // first triangle
            triangles[0] = 0;
            triangles[1] = 2;
            triangles[2] = 1;
            int tri = 3;

            // subsequent triangles
            for (int i = 1; i < vertexCount - 2; i++) // Triangle count will always be (vertex.count-2)*3
            {
                if (i % 2 == 0)
                {
                    triangles[tri] = i;
                    triangles[tri + 1] = i + 1;
                    triangles[tri + 2] = i + 2;
                }
                else
                {
                    triangles[tri] = i;
                    triangles[tri + 1] = i + 2;
                    triangles[tri + 2] = i + 1;
                }
                tri += 3;
            }

            mesh.triangles = triangles;

            // // calculate normals
            //
            // normals = new Vector3[vertices.length];
            //
            // // normal for vertex 0 
            // vector1 = verticesWorld[1] - verticesWorld[0];
            // vector2 = verticesWorld[2] - verticesWorld[0];
            //
            // if (Vector3.Angle(vector1, vector2) < 180) {
            //     normals[0] = Vector3.Cross(vector1, vector2);
            //     normals[0] = transform.InverseTransformDirection(normals[0].normalized);
            // } else {
            //     normals[0] = Vector3.Cross(vector2, vector1);
            //     normals[0] = transform.InverseTransformDirection(normals[0].normalized);
            // }
            //
            // // normals for subsequent vertices
            // for (i = 1; i < vertices.length - 1; i++) {
            //     vector1 = verticesWorld[i-1] - verticesWorld[i];
            //     vector2 = verticesWorld[i+1] - verticesWorld[i];
            //
            //     if (Vector3.Angle(vector1, vector2) < 180) {
            //         normals[i] = Vector3.Cross(vector1, vector2);
            //         normals[i] = transform.InverseTransformDirection(normals[i].normalized);
            //     } else {
            //         normals[i] = Vector3.Cross(vector2, vector1);
            //         normals[i] = transform.InverseTransformDirection(normals[i].normalized);
            //     }
            // }
            //
            // // normal for last vertex
            // vector1 = verticesWorld[vertices.length - 2] - verticesWorld[vertices.length - 1];
            // vector2 = verticesWorld[vertices.length - 3] - verticesWorld[vertices.length - 1];
            //
            // if (Vector3.Angle(vector1, vector2) < 180) {
            //     normals[vertices.length - 1] = Vector3.Cross(vector1, vector2);
            //     normals[vertices.length - 1] = transform.InverseTransformDirection(normals[vertices.length - 1].normalized);
            // } else {
            //     normals[vertices.length - 1] = Vector3.Cross(vector2, vector1);
            //     normals[vertices.length - 1] = transform.InverseTransformDirection(normals[vertices.length - 1].normalized);
            // }
            //
            // mesh.normals = normals;
           
        }

    }
}