- Home /
Procedural Mesh Editing Performance
Background:
I needed to make a mesh grid that I could change the height (y-axis) of individual quads at any coordinate I specified. I have the mesh created and am able to do exactly what I need to.
Problem:
I need to move many quads simultaneously. When I modify en mass, eg 100, (which is little compared to the many I intend on moving at once) my performance drops heavily.
My grid is made up of multiple meshes, so I find the relevant gameobject using the x and z coordinate and then edit the vertices in that gameobjects mesh
My code snippets:
private void GroupChange()
{
foreach (TileGroupClass group in groupList)
{
//if the last tiles height in the group does not equal the new target height, change the height.
if (group.tileList[group.tileList.Count - 1].y != group.newTargetHeight)
{
foreach (Vector3 tile in group.tileList)
{
ChangeHeight(tile, group);
}
}
}
}
void ChangeHeight(Vector3 tile, TileGroupClass group)
{
int x = Mathf.RoundToInt(tile.x);
int z = Mathf.RoundToInt(tile.z);
float height = group.newTargetHeight;
float heightChangeSpeed = group.speed;
//convert xz into chunkx and chunkz
int chunkX;
if (x >= chunkSize) chunkX = Mathf.RoundToInt(x / chunkSize);
else chunkX = 0;
int chunkZ = z % chunkSize;
if (z >= chunkSize) chunkZ = Mathf.RoundToInt(z / chunkSize);
else chunkZ = 0;
//get the mesh
GameObject curChunk = chunkArray[(chunkZ *chunkNum) +chunkX];
Mesh mesh = curChunk.GetComponent<MeshFilter>().mesh;
//set temporary arrays to modify
Vector3[] vertices = new Vector3[chunkSize * chunkSize * 4];
int[] triangles = new int[chunkSize * chunkSize * 6];
vertices = mesh.vertices;
triangles = mesh.triangles;
//set which quad vertices and triangles to modify
int changeNum = ((z % chunkSize) * chunkSize) + (x % chunkSize);
int v = changeNum * 4;
int t = changeNum * 6;
if(chunkX + chunkZ > 0)Debug.Log(changeNum);
float vertexOffset = cellSize * 0.5f;
Vector3 cellOffset = new Vector3((x - (chunkX * chunkSize)) * cellSize, 0, (z - (chunkZ * chunkSize)) * cellSize);
//save the quads current vertices position
Vector3 vertVect1 = new Vector3(vertices[v].x, vertices[v].y, vertices[v].z);
Vector3 vertVect2 = new Vector3(vertices[v + 1].x, vertices[v + 1].y, vertices[v + 1].z);
Vector3 vertVect3 = new Vector3(vertices[v + 2].x, vertices[v + 2].y, vertices[v + 2].z);
Vector3 vertVect4 = new Vector3(vertices[v + 3].x, vertices[v + 3].y, vertices[v + 3].z);
//move the quad to new position
vertices[v] = Vector3.MoveTowards(vertVect1, new Vector3(-vertexOffset, height, -vertexOffset) + cellOffset + gridOffset, heightChangeSpeed * Time.deltaTime);
vertices[v + 1] = Vector3.MoveTowards(vertVect2, new Vector3(-vertexOffset, height, vertexOffset) + cellOffset + gridOffset, heightChangeSpeed * Time.deltaTime);
vertices[v + 2] = Vector3.MoveTowards(vertVect3, new Vector3(vertexOffset, height, -vertexOffset) + cellOffset + gridOffset, heightChangeSpeed * Time.deltaTime);
vertices[v + 3] = Vector3.MoveTowards(vertVect4, new Vector3(vertexOffset, height, vertexOffset) + cellOffset + gridOffset, heightChangeSpeed * Time.deltaTime);
//set triangles
triangles[t] = v;
triangles[t + 1] = triangles[t + 4] = v + 1;
triangles[t + 2] = triangles[t + 3] = v + 2;
triangles[t + 5] = v + 3;
//set the values into the current mesh
mesh.vertices = vertices;
mesh.triangles = triangles;
//mesh.RecalculateNormals();
Vector3 temp = group.tileList[group.tileList.IndexOf(tile)];
//group.tileList[group.tileList.IndexOf(tile)].y = vertices[v + 3].y;
temp.y = vertices[v + 3].y;
group.tileList[group.tileList.IndexOf(tile)] = temp;
Debug.Log("tile height" + temp.x + " : " + temp.y + " : " + temp.z + " = " + group.tileList.Find(p => p == tile).y);
//group.curHeight = vertices[v + 3].y;
}
I understand this performance is because I'm updating many things using the update function, and wondered if there is a better way to implement what I'm trying to achieve. I have a great deal still to learn about programming.
Does anyone have any ideas?
Answer by Bunny83 · Dec 22, 2018 at 04:09 PM
Your main issue probably isn't that you are updating many things, but that you do so extremely inefficient. I'll go through the things as they appear in your code:
First of all this seems to be a bad house keeping solution:
GameObject curChunk = chunkArray[(chunkZ *chunkNum) +chunkX];
Mesh mesh = curChunk.GetComponent<MeshFilter>().mesh;
When you know you have to access the MeshFilter very frequently, you should store the references to the required objects in your chunkArray so you don't have to use GetComponent every time. Since you don't really care about the MeshFilter but only about the mesh object you probably should just store the Mesh object reference for each chunk.
The following 4 lines are just crazy:
Vector3[] vertices = new Vector3[chunkSize * chunkSize * 4];
int[] triangles = new int[chunkSize * chunkSize * 6];
vertices = mesh.vertices;
triangles = mesh.triangles;
The first 2 lines are completely useless and all they do is allocate a huge amount of memory which becomes immediately garbage since you overwrite the two arrays with the generated arrays from the mesh. So step one improvement which will reduce the generated garbage by half: Replace those 4 lines with those 2:
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
Though this will still allocate those two array each time you execute your method. Again if you know you want to manipulate the vertices very often, don't read the arrays each time, but store the arrays along with your chunk.
I don't know why so many people do such crazy things:
Vector3 vertVect1 = new Vector3(vertices[v].x, vertices[v].y, vertices[v].z);
Vector3 vertVect2 = new Vector3(vertices[v + 1].x, vertices[v + 1].y, vertices[v + 1].z);
Vector3 vertVect3 = new Vector3(vertices[v + 2].x, vertices[v + 2].y, vertices[v + 2].z);
Vector3 vertVect4 = new Vector3(vertices[v + 3].x, vertices[v + 3].y, vertices[v + 3].z);
Reading vertices[v]
will return the vector3 that is stored in the array at index "v". You only access the x component. Then you again read the same vector3 and only read the y component. And once again just to read the z component. Then you run the constructor of Vector3 to create the very exact same vector3 that you get back when you read vertices[v]
. What's the point? Vector3 is a value type so when you assign it to another variable you get a copy of it's value. The 4 vector3 variables are also unnecessary since MoveTowards does not change the values you pass in. Actually it can't because the method only receives a copy of the value.
So you can simply do
vertices[v] = Vector3.MoveTowards(vertices[v], .......
It looks like that the number of quads in your mesh doesn't really change. There's no need to recreate the triangles array every time if it ends up exactly the same.
Finally the absolute killer, those 3 lines:
Vector3 temp = group.tileList[group.tileList.IndexOf(tile)];
temp.y = vertices[v + 3].y;
group.tileList[group.tileList.IndexOf(tile)] = temp;
it looks like "tileList" is an array of Vector3s, is that right? The "IndexOf" method of an array has to iterate through your whole array (half the array on average) to find that particular vector3 value. You actually do this twice for every element in that tileList. So if there are 1000 elements in your array, you essentially doing 1 million vector3 comparisons. if there are 10k elements you do 100 million comparisons. Next thing is your "temp" value that you are reading from your tileList is actually the same as the vectors in "tile". You're searching for the index of an element that is equal to vector3 tile and then you just read that value. So your first line would be the same as just writing
Vector3 temp = tile;
Boom half the iterations saved.
However you can avoid all those IndexOf stuff by just passing the index of the tile you want to change the height of instead of the actual position. Since you pass the group along you can directly access the vector3 in that tileList by using the index.
So change the inner loop to
int count = group.tileList.Length;
for (int i = 0; i < count; i++)
{
ChangeHeight(i, group);
}
And your ChangeHeight method to
void ChangeHeight(int tileIndex, TileGroupClass group)
{
Vector3 tile = group.tileList[tileIndex];
// [ ... ]
And at the end of your method instead of your temp variable, you can simply do
tile.y = vertices[v + 3].y;
group.tileList[tileIndex] = tile;
Thank you for replying mate! I'm going to have a look through the code and make some serious modifications!
I didn't include all the code, but I have a question in regards to the bad housekeeping: As I am calling other meshes, not calling this gameobjects mesh, wouldn't I need to use something along those lines?
Your answer
Follow this Question
Related Questions
Holes in procedural mesh 0 Answers
Set vertex positions aligned to a grid 2 Answers
What's wrong with this simple mesh manipulation? 1 Answer
Getting mesh length (x,z) 1 Answer
How can I remove redundant vertices in custom mesh? 0 Answers