The question is answered, right answer was accepted
Combine Meshes combine with current mesh
Ok so I have a tree script that generates trees in a random location within a circle. To stop the trees from generating ontop of each other, when the raycast to generate the tree is fired downwards, if it collides with the tree collider it will re-raycast until it finds a point without an exisiting tree. For performance reasons I needed to merge the tree meshes together (each tree was making 3 draw calls, one for shadow, one for the light, and one for the object itself) and with up to 40^2 trees placeable, was making waaaaay too many batches. So long story short I made a mesh merge script that gets the mesh from every object with the tag "firTree" and then put those into the combineInstance variable. Then I use the combineMeshes function to put that into an empty gameObjects mesh filter, and disable all of the meshRenderers of the individual trees. Heres the Problem: Whats going on is every frame it is getting the mesh data from the individual trees and combining them. If I was to make thousands of trees, the fps would die from merging all of them every frame. Therefore I wanted to Destroy() the individual trees once they had been combined, however the trees disappear. So.....I made another combineInstance slot in its array for the current combined mesh, so that what would happen is it would combine, then destroy the original objects, and only combine new trees. However Unity throws up the error :
Cannot combine into a mesh that is also in the CombineInstances input: 5,0_firTree
UnityEngine.Mesh:CombineMeshes(CombineInstance[], Boolean, Boolean)
meshMerge:Update() (at Assets/treeTool/firTree/meshMerge.cs:50)
so I assume that means it is already combining it to the current mesh?? but if so then why do the trees disappear when I destroy the individual ones? They should still be visible after I remove the original mesh data, right? Or does it need to continue referencing it? I don't even know what unity is doing xD
Anyway, just in case its helpful, ive got the combine script. If you are going to run it, remember to tag your objects as "firTree" for it to work ;)
using System.Collections;
public class meshMerge : MonoBehaviour {
//the meshFilter of the combined tree object
private MeshFilter filter;
//the meshFilter array of gameObects to combine
private Component[] meshFilterArray;
//objects to combine
public GameObject[] toCombine;
//the mesh instance to apply, special type for meshcombine function
public CombineInstance[] toApply;
void Start () {
//sets combined filter to the current mesh filter (for reference optimisation)
filter = GetComponent<MeshFilter>();
//makes a plain mesh, with no current data (for merging purposes)
//there always needs to be a mesh in this location, otherwise when merging, the first time will generate
//an error for no current mesh to combine with
filter.mesh = new Mesh();
}
void Update () {
//will put all gameObjects with the tag firTree in the array of gameObjects to combine
toCombine = GameObject.FindGameObjectsWithTag ("firTree");
int i = 0;
//the combine instance for the combineMeshes, sets length to the gameObject array, +1 for existing mesh
toApply = new CombineInstance[toCombine.Length + 1];
//repeats for every gameObject
foreach(GameObject tree in toCombine){
//passes in the mesh data
toApply[i].mesh = tree.GetComponent<MeshFilter> ().mesh;
//converts the local positions to world positions in a matrix, so the combineMesh can use it (no idea why)
toApply[i].transform = tree.GetComponent<MeshFilter> ().transform.localToWorldMatrix;
Destroy (tree.gameObject);
//disables the mesh renderer (commented out for experimentation purposes)
//tree.GetComponent<MeshRenderer>().enabled = false;
//increments i for the array location
i ++;
}
//does the same process to the existing mesh (adds it to the combine instance
toApply[i].mesh = filter.mesh;
toApply[i].transform = filter.transform.localToWorldMatrix;
//joins all meshes, set other parameters to true so it uses the matrix positions and make them a single mesh
filter.mesh.CombineMeshes (toApply, true, true);
}
}
if you can enlighten me as to what I'm doing wrong, I'm very grateful. Thanks ;)
Oh and another thing you might be able to help me with, is it only works up to the vertices limit of 67,000. anyone know the most concise way of checking its size and then making a new mesh (submesh?) thanks
ahhh don't worry, figured it out. All you gotta do is have a mesh array, pretty simple if you learn how to google properly xD
Ok so I assume whats happening is the mesh is being referenced back to the individual trees mesh. So that means every frame its combining the same objects?? this must destroy performance?? Does anyone know a way I can make the mesh independent? Or have I got this all completely wrong? I literally don't know what I'm doing....
Ok so if I add a cube to the joined mesh and run it, the cube mesh is deleted. So it must combine all the objects in the array and then replace the mesh in it, as opposed to adding to the current mesh. However I cant use the current mesh in the array or I get that error, nobody can help? :(
Answer by joshua-lyness · Feb 14, 2016 at 02:43 PM
Hmm I figured it out myself. All that I needed to do was add a line before combining, to make a new mesh. So: filter.mesh = new mesh();
that way I wasn't writing to a mesh I had just read from, no idea why unity doesn't let you do that. If anyone could explain that would be great, but otherwise, its sorted.
Oh and a little tip if anyone uses my code, don't delete the destroy part in the foreach, otherwise the mesh size increases exponentially and causes unity to crash xD
Trying to understand what I was trying to do above is impossible. Now that I have some more experience, I'll explain. Say you have an array of meshes you want to combine together (or maybe just a couple $$anonymous$$esh variables, same applies.) In my case, I have a for loop that generates a mesh from a Bezier Curve. This here is where I apply the vertices, faces, normals, and UV's.
mesh[curveIndex] = new $$anonymous$$esh();
mesh[curveIndex].vertices = verts;
mesh[curveIndex].normals = normals;
mesh[curveIndex].triangles = triangles;
mesh[curveIndex].uv = uvs;
Now I made a combine instance before the for loop, which you set to be as long as the number of meshes you want to combine.
CombineInstance[] combineInstance = new CombineInstance[length];
The combineInstance type has few properties that need to be assigned. The mesh, and the position relative to world space. Otherwise the meshes wont know where they need to be, relative to one another. When you think about it, the only way a mesh knows where to draw is the position of the vertices, which are all relative to world space. You just need to tell the CombineInstance that the vertices are relative to world coordinates.
combineInstance[curveIndex].mesh = mesh[curveIndex];
combineInstance[curveIndex].transform = buildGameobject[roadIndex].transform.localToWorld$$anonymous$$atrix;
It's that transform.localToWorld$$anonymous$$atrix that's important. I've never had a case where it's not been local to world positions. If you just want to combine a few meshes in your scene to one object for performance reasons, and you dont want the meshes to move, then localtoworld is your friend.
Once you have specified every mesh and transform of the combine instance (the submesh indexes too if you need to use them), then you can combine! Essentially, this is the point your code turns the combineinstance into a mesh, and puts it in a variable.
buildGameobject[roadIndex].GetComponent<$$anonymous$$eshFilter>().mesh.Combine$$anonymous$$eshes(combineInstance, true, true);
The (..., true, true) bit for me isnt really relevant, since I have no submeshes, and I always make sure usematrices is true, since I want the combineInstance to use the transform positions I gave it earlier.
I'm not great at explaining, but heres a couple links to documentation that unity provides. https://docs.unity3d.com/ScriptReference/$$anonymous$$esh.Combine$$anonymous$$eshes.html https://docs.unity3d.com/ScriptReference/CombineInstance.html
Remember :
1 : $$anonymous$$ake combineInstance()
1.5 : Get the meshes
2 : Put the meshes in combineInstance[].mesh
3 : Set combineInstance[].transform to mesh.transform.localToWorld$$anonymous$$atrix
4 : Finally, final$$anonymous$$esh.GetComponent().mesh.Combine$$anonymous$$eshes(combineInstance, true, true)
I hope this has helped, I tried anyway :D
Hey string username, I know youve tried adding comments to this, but for whatever reason they won't show up. $$anonymous$$ake a new question and tag me in it, and ill try to help.