Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Possible fixes for problems with Unity TreeEditor Nodes not being removed from the TreeData file

Discussion in 'World Building' started by TimCoster, Jan 21, 2019.

  1. TimCoster

    TimCoster

    Joined:
    May 19, 2016
    Posts:
    16
    On 29 dec I reported a bug with the built in Tree Editor that I may have found solutions for. Also see video's in the end.

    When a tree branchgroup from a tree hierarchy is removed in the inspector, the corresponding node(s) in the Nodes list of the TreeData scriptable object aren't also removed.
    Because of this the TreeData file gets unnecessarily large very fast when a lot of branchgroups are added and removed. For each branchgroup that is added in the inspector one BranchGroupNode is created and one Node in the data file, but there is also one node created for every extra branch when the frequency of a branch group is increased.

    Using the tree editor is a trial and error process is what the manual says so this adding and removing of branches shouldn't cause problems like this I think.

    There are two things that go wrong:
    1. When the last child in a hierarchy of branch groups is removed, the corresponding Node from the Branch Groups list is removed from the data file as should but not the corresponding Nodes from the Nodes list.
    2. When a Branch Group that isn't the last child in the hierarchy is removed, so a middle one, both the corresponding nodes from the Branch Groups list and the Nodes from the Nodes list aren't removed. This is because only the child nodes are removed along with a group but not the children's children.
    Because I really really want to use the Unity tree system in one of my projects I started searching for a possible solution on the internet and I came across the TreeEditor source code on the Unity Github page.
    I copied the entire TreeEditor module and put everything in a separate namespace in my project so I could try to debug it myself. I have been working on it for a week and I think I may have found solutions for these problems.

    The first problem I found is in the /Modules/TreeEditor/TreeData.cs file in the DeleteGroup(TreeGroup g) function on line 271, where it loops trough the groups 'nodes' array to delete them..:

    Code (CSharp):
    1.            
    2.  
    3. // remove nodes
    4.  
    5. for (int i = g.nodes.Count - 1; i >= 0; i--)
    6.  
    7. {
    8.  
    9.     DeleteNode(g.nodes[i], false);
    10.  
    11. }
    12.  
    13.  
    g.nodes.Count returns zero here because a branch group only stores references to nodes by storing their nodeID's, so no nodes are deleted. I think this function is supposed to remove the nodes from the list of nodes based on the branch group's array of nodeID's, so I tried the following which seems to work:

    Code (CSharp):
    1.    
    2.  
    3. // remove nodes
    4.  
    5. for (int i = g.nodeIDs.Length - 1; i >= 0; i--)
    6.  
    7. {
    8.  
    9.     DeleteNode(GetNode(g.nodeIDs[i]), false);
    10.  
    11. }
    12.  
    13.  
    This only takes care of the first problem. For the second problem, when a Branch node is removed that is not the last child in the hierarchy but somewhere in the middle, I came up wit this solution:

    The problem is that each node only stores references to the attached child nodes in the hierarchy in an array but not to the children's children.
    Instead of also storing all the nodes from the children's children so they can be removed later I created a OnDeleteTreeGroupBranch() method in the TreeGroupBranch class that when it's called from the TreeData class, first loops trough all of it's own children and calls OnDeleteTreeGroupBranch() on them to remove themselves, before it removes itself from the TreeData branchgroups array.
    So each child calls its own children to remove themselves before removing itself.

    Again in the /Modules/TreeEditor/TreeData.cs file in the DeleteGroup(TreeGroup g) function after line 271:

    Code (CSharp):
    1.  
    2.  
    3.         public void DeleteGroup(TreeGroup g)
    4.         {
    5.             // remove nodes
    6.             for (int i = g.nodeIDs.Length - 1; i >= 0; i--)
    7.             {
    8.                 DeleteNode(GetNode(g.nodeIDs[i]), false);
    9.             }
    10.            
    11.  
    12.             // Remove branchgroups child branchgroups, and also its children's children
    13.             if (g.GetType() == typeof(TreeGroupBranch))
    14.             {
    15.                 for (int i = g.childGroupIDs.Length-1; i >= 0; i--)
    16.                 {
    17.                    (GetGroup(g.childGroupIDs[i]) as TreeGroupBranch)?.OnDeleteTreeGroupBranch(this);
    18.                    (GetGroup(g.childGroupIDs[i]) as TreeGroupLeaf)?.OnDeleteTreeGroupLeaf(this);
    19.                 }
    20.                
    21.                 branchGroups = ArrayRemove(branchGroups, g as TreeGroupBranch);
    22.             }
    23.             else if(g.GetType() == typeof(TreeGroupLeaf))
    24.             {
    25.                 for (int i = g.childGroupIDs.Length-1; i >= 0; i--)
    26.                 {
    27.                     (GetGroup(g.childGroupIDs[i]) as TreeGroupLeaf).OnDeleteTreeGroupLeaf(this);              
    28.                 }
    29.                
    30.                 leafGroups = ArrayRemove(leafGroups, g as TreeGroupLeaf);
    31.             }
    32.  
    33.             // unlink..
    34.             SetGroupParent(g, null);
    35.         }
    36.  
    37.  
    The OnDeleteTreeGroupBranch() method that I added in the TreeGroupBranch class:

    Code (CSharp):
    1.  
    2.  
    3.         // When this TreeGroup is deleted it will call this function
    4.         // on the next child which calls it on the next etc..
    5.         public void OnDeleteTreeGroupBranch(TreeData owner)
    6.         {
    7.             foreach(int id in childGroupIDs)
    8.             {
    9.                 (owner.GetGroup(id) as TreeGroupBranch)?.OnDeleteTreeGroupBranch(owner);
    10.                 (owner.GetGroup(id) as TreeGroupLeaf)?.OnDeleteTreeGroupLeaf(owner);
    11.             }
    12.            
    13.             owner.branchGroups = owner.ArrayRemove(owner.branchGroups, owner.GetGroup(uniqueID) as TreeGroupBranch);
    14.            
    15.             foreach (int id in nodeIDs )
    16.             {
    17.                 owner.DeleteNode(owner.GetNode(id));
    18.             }
    19.         }
    20.  
    21.  
    I also added a OnDeleteTreeGroupLeaf() method in the TreeGroupLeaf class:

    Code (CSharp):
    1.  
    2.  
    3.         public void OnDeleteTreeGroupLeaf(LivingTreeData owner)
    4.         {
    5.             owner.leafGroups = owner.ArrayRemove(owner.leafGroups, owner.GetGroup(uniqueID) as TreeGroupLeaf);
    6.  
    7.             foreach (int id in nodeIDs )
    8.             {
    9.                 Debug.Log("deleting node");
    10.                 owner.DeleteNode(owner.GetNode(id));
    11.             }
    12.         }
    13.  
    14.  
    I also made the ArrayRemove method public so it can be accessed from the TreeGroupBranch class but I don't know if this is the safest way.

    Videos:

    Problem 1


    Problem 2


    Fixed


    I don't know if this is the best possible solution using these methods and it can definitely be improved but as u can see in the video's it seems to work now, no data is left behind, so I'm happy that I can be more free and creative with designing tree's and that I can use it for my project.
    One of the things I did that made debugging a lot easier was that I moved all the nodes IDs variables to the top of their classes so it's easier to see them at the top of the list in the inspector and I would like to suggest to also maybe change that in the source code.

    Another small thing,.. In the Modules/TreeEditor/TreeData.cs script in the UpdateMesh() function there is a comment that says “// Copy mesh data to static arrays.. surely there must be a faster way to do this” before a loop is being used to copy values from a list to arrays and then it copies the arrays to the mesh.vertices and mesh.normals etc. arrays.
    A faster way to do this maybe is with System.Linq.Select():

    So instead of this:

    Code (CSharp):
    1.  
    2.  
    3. // Copy mesh data to static arrays.. surely there must be a faster way to do this
    4. Vector3[] tmpPos = new Vector3[verts.Count];
    5. Vector3[] tmpNor = new Vector3[verts.Count];
    6. Vector2[] tmpUV0 = new Vector2[verts.Count];
    7. Vector2[] tmpUV1 = new Vector2[verts.Count];
    8. Vector4[] tmpTan = new Vector4[verts.Count];
    9. Color[]   tmpCol = new Color[verts.Count];
    10.  
    11. for (int i = 0; i < verts.Count; i++)
    12. {
    13.     tmpPos[i] = verts[i].pos;
    14.     tmpNor[i] = verts[i].nor;
    15.     tmpUV0[i] = verts[i].uv0;
    16.     tmpUV1[i] = verts[i].uv1;
    17.     tmpTan[i] = verts[i].tangent;
    18.     tmpCol[i] = verts[i].color;
    19.  
    20. }
    21.  
    22. // assign vertex properties
    23. mesh.vertices = tmpPos;
    24. mesh.normals = tmpNor;
    25. mesh.uv = tmpUV0;
    26. mesh.uv2 = tmpUV1;
    27. mesh.tangents = tmpTan;
    28. mesh.colors = tmpCol;
    29.  
    30.  


    Do this:



    Code (CSharp):
    1.  
    2.  
    3. using System.Linq;
    4.  
    5.  
    6.  
    7. // assign vertex properties
    8. mesh.vertices = verts.Select(i => i.pos).ToArray();
    9. mesh.normals = verts.Select(i => i.nor).ToArray();
    10. mesh.uv = verts.Select(i => i.uv0).ToArray();
    11. mesh.uv2 = verts.Select(i => i.uv1).ToArray();
    12. mesh.tangents = verts.Select(i => i.tangent).ToArray();
    13. mesh.colors = verts.Select(i => i.color).ToArray();
    14.  
    15.  
     
    chanfort likes this.
  2. TimCoster

    TimCoster

    Joined:
    May 19, 2016
    Posts:
    16
    Found a solution for the tree Hierarchy stats not displaying:

    Screenshot 2019-02-18 at 12.28.05.png Screenshot 2019-02-18 at 12.28.22.png

    Inside TreeEditor.cs, line 2552, instead of this:

    Code (CSharp):
    1.            
    2. // draw stats
    3. MeshFilter m = renderer.GetComponent<MeshFilter>();
    4. if (m && m.sharedMesh && renderer)
    5. {
    6.     int vs = m.sharedMesh.vertices.Length;
    7.     int ts = m.sharedMesh.triangles.Length / 3;
    8.     int ms = renderer.sharedMaterials.Length;
    9.     Rect labelrect = new Rect(hierachyDisplayRect.xMax - 80 - 4, hierachyDisplayRect.yMax + offset.y - 40 - 4, 80, 40);
    10.  
    11.     string text = TreeEditorHelper.GetGUIContent("Hierachy Stats").text;
    12.     text = text.Replace("[v]", vs.ToString());
    13.     text = text.Replace("[t]", ts.ToString());
    14.     text = text.Replace("[m]", ms.ToString());
    15.     text = text.Replace(" / ", "\n");
    16.  
    17.     GUI.Label(labelrect, text, EditorStyles.helpBox);
    18. }
    19.  
    Don't really remember what it was supposed to look like but maybe do this:

    Code (CSharp):
    1.    
    2. MeshFilter m = renderer.GetComponent<MeshFilter>();
    3. if (m && m.sharedMesh && renderer)
    4. {
    5.     int vs = m.sharedMesh.vertices.Length;
    6.     int ts = m.sharedMesh.triangles.Length / 3;
    7.     int ms = renderer.sharedMaterials.Length;
    8.     Rect labelrect = new Rect(hierachyDisplayRect.xMax - 80 - 4, hierachyDisplayRect.yMax + offset.y - 50 - 4, 80, 50);
    9.  
    10.     string text = TreeEditorHelper.GetGUIContent(
    11.         string.Format("Hierarchy Stats\n vertices: {0}\n triangles: {1}\n materials: {2}", vs, ts, ms)
    12.     ).text;
    13.  
    14.     GUI.Label(labelrect, text, EditorStyles.helpBox);
    15. }
    16.  
    There is also a 'r' missing from 'Hierachy' :)

    I want to suggest one other thing that could improve the tree editor a little bit and that is to make a 'delete all branches' button next to the delete branch button. This is easy to do when the delete tree branch functionality actually works and removes all the nodes from the treedata like how I suggested in the original post. A button that deletes everything really helps to quickly experiment with different branch hierarchies. I have it working now from a separate script by doing this:

    Code (CSharp):
    1.  
    2.  
    3. public void DeleteAllBranches()
    4. {
    5.     int[] groupIDs = livingTreeData.root.childGroupIDs;
    6.     for(int i=0; i < groupIDs.Length; i++)
    7.         treeData.DeleteGroup(treeData.GetGroup(groupIDs[i]));
    8. }
    9.  
    10.