- Home /
Generating a procedural cylindrical mesh following a path
Hi,
I'm trying to procedurally generate meshes following paths generated by a space colonization algorithm. I am trying to do this by generating rings of vertices around each point in the path, and rotating them to face the previous point in the path (if it exists), which will hopefully produce cylindrical meshes following the path. However my code is producing some strange artifacts, especially as i increase the radius of the mesh generated. Because of this I think it may be due to the circles of vertices overlapping when they are rotated at increased radiuses, but I can't work out how to set minimum and maximum rotations for these circles based on the radius of the cylinder.
Any help or suggestions of alternative, better ways of producing similar results would be greatly appreciated as I'm sure this is far from the best way to go about it.
Here is the code responsible for taking a non-branching series of nodes and using their positions to generate the mesh, and some screenshots of the results.
// function generate meshes from non-branching paths
private Mesh[] GenerateMeshFromPaths(List<Node>[] inputPaths)
{
GameObject[] pathsToDelete = GameObject.FindGameObjectsWithTag("Mesh");
foreach (GameObject pathToDelete in pathsToDelete)
{
Destroy(pathToDelete);
}
// generate a ring of vertices around each node in each path
// generate a mesh for each path
Mesh[] pathMeshes = new Mesh[inputPaths.Length];
// generate a 'stretch' containing important information about each mesh in the path
stretches.Clear();
// loop through paths
for (int i = 0; i < inputPaths.Length; i++)
{
pathMeshes[i] = new Mesh();
#region vertices
// loop through nodes in each path
List<Vector3> vertices = new List<Vector3>();
List<BoneWeight> boneWeights = new List<BoneWeight>();
vertices.Add(new Vector3(0, 0, 0) + inputPaths[i][0].position);
foreach (Node node in inputPaths[i])
{
// get direction for ring to face
// https://answers.unity.com/questions/1744376/get-vertices-of-circle-facing-any-direction.html
Vector3 normal = Vector3.zero;
if (node.parent != null)
{
// if node has parent set parent node as direction to face
normal = (node.parent.position - node.position).normalized;
}
else if (node.child != null)
{
// if node has no parent but has child then face child
normal = (node.child.position - node.position).normalized;
}
else
{
// if node isolated then direction doesn't matter
}
// return vector perpendicular to normal
Vector3 tangent = Vector3.Cross(normal, (normal == Vector3.up) ? Vector3.forward : Vector3.up).normalized;
// loop over number of segments per node-ring
for (int j = 0; j < growthSettings.segments; j++)
{
float angle = j * 360f / growthSettings.segments;
float x = growthSettings.radius * Mathf.Cos(angle * Mathf.Deg2Rad) + node.position.x;
float y = growthSettings.radius * Mathf.Sin(angle * Mathf.Deg2Rad) + node.position.y;
// float z = node.position.z + growthSettings.segmentLength;
float z = node.position.z;
Vector3 rotatedVertex = node.position + (Quaternion.AngleAxis(j / (float)growthSettings.segments * 360f, normal) * tangent) * growthSettings.radius;
// Vector3 rotatedVertex = node.position + (Quaternion.AngleAxis((j / (float)growthSettings.segments) * 360f, normal) * tangent);
Vector3 newVertex = new Vector3(x, y, z);
vertices.Add(rotatedVertex);
}
}
vertices.Add(new Vector3(0, 0, 2f * growthSettings.radius + (growthSettings.segmentLength * inputPaths[i].Count)) + inputPaths[i][0].position);
pathMeshes[i].vertices = vertices.ToArray();
#endregion
#region triangles
List<int> triangles = new List<int>();
// loop over rings of vertices, adding triangle indexes
// for (int ring = 0; ring < inputPaths[i].Count-1; ring++)
for (int ring = 0; ring < (inputPaths[i].Count - 1); ring++)
{
int ringOffset = ring * growthSettings.segments;
// int ringOffset = 1 + ring * growthSettings.segments;
// loop over segments
for (int j = 0; j < growthSettings.segments; j++)
{
int seamOffset = j != growthSettings.segments - 1 ? 0 : growthSettings.segments;
triangles.Add(ringOffset + j);
triangles.Add(ringOffset + j + 1 - seamOffset);
triangles.Add(ringOffset + j + 1 - seamOffset + growthSettings.segments);
triangles.Add(ringOffset + j + 1 - seamOffset + growthSettings.segments);
triangles.Add(ringOffset + j + growthSettings.segments);
triangles.Add(ringOffset + j);
}
}
pathMeshes[i].triangles = triangles.ToArray();
#endregion
NormalSolver.RecalculateNormals(pathMeshes[i], 0);
pathMeshes[i].RecalculateTangents();
#region UV
List<Vector2> uv = new List<Vector2>();
uv.Add(Vector3.zero);
for (int ring = 0; ring < (inputPaths[i].Count); ring++)
{
float v = ring / growthSettings.rings;
for (int k = 0; k < growthSettings.segments; k++)
{
float u = k / (float)(growthSettings.segments - 1);
uv.Add(new Vector2(u, v * (inputPaths[i].Count + 1)));
// uv.Add(new Vector2(0, 1));
}
}
uv.Add(Vector2.one);
pathMeshes[i].uv8 = uv.ToArray(); // Store copy of UVs in mesh.
pathMeshes[i].uv = uv.ToArray();
#endregion
//pathMeshes[i].SetUVs(0, pathMeshes[i].uv8);
}
for (int i = 0; i < pathMeshes.Length; i++)
{
#region stretch
// important information about path section held in stretch script
GameObject newStretch = Instantiate(stretchPrefab, transform);
newStretch.GetComponent<Stretch>().InitializeStretch(inputPaths[i][0], inputPaths[i][inputPaths[i].Count - 1], pathMeshes[i], meshMat);
//newStretch.GetComponent<Stretch>().SetStretch();
stretches.Add(newStretch);
#endregion
}
return pathMeshes;
}
Answer by homer_3 · Apr 22 at 02:59 PM
I've found Tubular (https://github.com/mattatz/unity-tubular) to be great and very easy to use for this kind of thing.
Hey, thanks so much for the answer, this looks extremely useful and likely to succeed. I just need to get a chance to have a go at implementing it before I accept the answer.