Tree Avoidance on Very Large Terrains

Dear sir or madam,

I’m experimenting with a 3 km² terrain, with 300,000 trees, which I managed to get running very smoothly thanks to Unity’s billboarding system.

Upon generating a nav mesh -which took very long- I found myself in the company of a very generous 179 MB of navigation data which I may very conservatively say is ‘somewhat unwieldy’.

I removed all trees by way of Mass Placing an astonishing 0 trees without keeping the existing ones, re-baked the nav mesh and then found myself with a delightfully modest 4 MB of navigation data. Splendid.

My next step would have been to have non-player characters avoid trees via local navigation by adding Nav Mesh Obstacle components to the tree prefabs. Surprisingly the nav mesh agent cheerfully ignores all trees after doing so.

Some casual reading of related literature has implied that with the exception of colliders, no components make it to the actual tree instances. Is that true?

What do the bright fellows of the Unity Community think about this predicament and how would they themselves tackle navigation on such a very large complex terrain?

Thank you for your time.

There is something horribly wrong with how they handle trees. creating a navmesh for 6 1km terrains with a decent number of trees ate up 8gb during generation. I just stopped at that point.

I created meshes from my terrains, and then placed capsule colliders where my trees are, and it worked fine without the crazy memory usage.

I suspect that they use hole punching for trees, and that’s actually the culprit. Or something that’s not just normal geometry, because normal geometry doesn’t bloat ram or the nav data like I see with trees.

The one time I patiently waited for the nav mesh to be generated with the trees still on it, it indeed created a diamond shaped hole for every tree on the mesh. That’s clever and it works well for small terrains but it scales poorly.

I have a lot of trees so I’m not sure if it’s feasible to place a capsule collider on every single tree. I guess I can try it out just to see how it performs. My current battle plan is to cache all the locations of my trees in a spatially accessible way like a grid so that I can figure out which trees are close to the player and then place colliders on them from a pool of colliders.

I will report back once I’ve had time to try these things out and share my findings.

The capsules I placed were temporary, I remove them as soon as the navmesh generation is complete.

You might take a look at some code I posted in another thread for dynamically placing colliders on trees (and other objects). It uses a spatial grid.

1 Like

Thanks, that sounds like exactly what I was going to build. You might have saved me a bunch of work there snacktime.

More people should know about this. Your thread did not show up when I googled the matter. I will spread the gospel.

A bit late for the party - yes adding navmeshobstacle to trees in makes sense if the trees are sparse enough to have navigation go through.

Colliders on trees are handled in a special way - there are no other components that get instantiated on terrain in a similar way. Unfortunately this means some extra work is required for placing obstacles.
You’ll need to keep a budget of instances and place the obstacles only where they make a visible impact - e.g. close to camera or player character - below is a proof of concept script that decorates the nearest trees with a prefab.

// DecorateNearbyTrees.cs
using UnityEngine;
using System.Collections.Generic;

// Decorate nearby trees on terrain by prefab
// Decorates the closest "count" trees on "terrain" with a "decorationPrefab" each
public class DecorateNearbyTrees : MonoBehaviour {
   public Terrain terrain;
   public GameObject decorationPrefab;
   public int count = 10;

   // Info for sorting trees spatially
   struct TreeData {
     public float x,y,z;
     public int id;
     public Vector3 pos { get {return new Vector3 (x,y,z);}  set {x = value.x; y=value.y; z=value.z;}}
   }

   Vector3 terrainSize;
   TreeData[] treeData;
   List<Transform> decorationPool = new List<Transform>();

   static float localSearchPosX;
   static float localSearchPosZ;

   void Start () {
     if (terrain == null || decorationPrefab == null) {
       Debug.LogError ("Terrain and decorationPrefab required");
       return;
     }

     var data = terrain.terrainData;
     terrainSize = data.size;

     // Sample tree info for spatial ordering
     int treeCount = data.treeInstanceCount;
     treeData = new TreeData[treeCount];
     for (int i = 0; i < treeCount; ++i) {
       TreeInstance t = data.GetTreeInstance(i);
       treeData[i].pos = t.position;
       treeData[i].id = t.prototypeIndex;
     }

     // Construct pool of GO's to use for decorating trees
     for (int i = 0; i < count; ++i) {
       GameObject go = (GameObject)GameObject.Instantiate (decorationPrefab, Vector3.zero, Quaternion.identity);
       decorationPool.Add (go.transform);
     }
   }

   // Sort trees by 2D distance to localSearchPos
   static int TreeDistance2DCompare (TreeData a, TreeData b) {
     float dxa = a.x - localSearchPosX;
     float dza = a.z - localSearchPosZ;
     float dxb = b.x - localSearchPosX;
     float dzb = b.z - localSearchPosZ;
     float daSq = dxa*dxa + dza*dza;
     float dbSq = dxb*dxb + dzb*dzb;
     if (daSq < dbSq) return -1;
     if (daSq > dbSq) return 1;
     return 0;
   }

   void Update () {
     if (treeData == null)
       return;

     // Prepare for sort callback
     Vector3 worldPos = transform.position;
     localSearchPosX = worldPos.x / terrainSize.x;
     localSearchPosZ = worldPos.z / terrainSize.z;

     // Sort is slow and allocates GC memory (aux storage for the non-recursive qsort impl.)
     // Ultimately one should use a 2D spatial hash lookup for better performance
     System.Array.Sort (treeData, TreeDistance2DCompare);

     for (int i = 0; i < count; ++i) {
       Vector3 treePos = Vector3.Scale(terrainSize, treeData[i].pos);
       decorationPool[i].position = treePos;
     }
   }
}

In this case the decoration could be a prefab with a NavMeshObstacle component (capsule).

Is this using Unity5 ? 8Gb sounds like a problem. NavMesh building is done mostly local - and memory requirements should be reasonable. please report a bug on this.

I very much appreciate the reply, especially the fact that you took the time to write some example code.

Sounds like the pool solution makes the most sense. Assured that my approach was not overcomplicating things I feel that I have enough input to implement the solution.

Thanks again Jakob_Unity and snacktime for sharing your experience. I hoop the Google crawler is kind to us and more people find this thread.

I tried replicating the issue in a separate project, one that’s not 3gb, but of course it works fine there. The only obvious difference being that instead of 100 terrains I used 4. I increased the detail on the 4 terrains to have around the same number of trees and static mesh detail objects, but couldn’t trigger it.

I’m going to do some more testing, don’t really want to submit a bug report until I get it narrowed down a bit.

Ok so narrowed it down a bit. The memory bloat happens when you have height mesh enabled. Memory use stays relatively constant until it hits the storing tiles phase, and there it skyrockets.