Unity terrain trees with multiple or complex collider

Currently, A* Pathfinding will only look for a single collider on the root game object of terrain trees. Here is a modification to CollectTreeMeshes() in RecastMeshGatherer.cs that will allow for multiple colliders with offset positions/rotations.

Note: If you are using “random rotation” on your trees, Unity will never rotate the colliders (only the mesh). This is a limitation of Unity terrain. So in general, never use “random rotation” on terrain trees that have more than a single, straight capsule collider.

This modification will ignore trees without any colliders. For my purposes I like to have small shrubs that need to be “trees” but I do not want colliders or path blocked. You can add the code back for that if you prefer from the original CollectTreeMeshes().

void CollectTreeMeshes(Terrain terrain, List<RasterizationMesh> result)
        {
            // modified jb
            TerrainData data = terrain.terrainData;
            Vector3 treeColliderPos;
            Quaternion treeColliderRot;

            for (int i = 0; i < data.treeInstances.Length; i++)
            {
                TreeInstance instance = data.treeInstances[i];
                TreePrototype prot = data.treePrototypes[instance.prototypeIndex];
                // Make sure that the tree prefab exists
                if (prot.prefab == null)
                {
                    continue;
                }
                Collider[] treeColliders = prot.prefab.transform.GetComponentsInChildren<Collider>();
                var treePosition = terrain.transform.position + Vector3.Scale(instance.position, data.size);
                var scale = new Vector3(instance.widthScale, instance.heightScale, instance.widthScale);
                for (int x = 0; x < treeColliders.Length; x++)
                {
                    treeColliderPos = treePosition + Vector3.Scale(treeColliders[x].gameObject.transform.position - prot.prefab.transform.position, scale);
                    treeColliderRot = treeColliders[x].gameObject.transform.rotation;
                    // Generate a mesh from the collider
                    RasterizationMesh mesh = RasterizeCollider(treeColliders[x], Matrix4x4.TRS(treeColliderPos, treeColliderRot, scale));
                    // Make sure a valid mesh was generated
                    if (mesh != null)
                    {
                        // The bounds are incorrectly based on collider.bounds.
                        // It is incorrect because the collider is on the prefab, not on the tree instance
                        // so we need to recalculate the bounds based on the actual vertex positions
                        mesh.RecalculateBounds();
                        result.Add(mesh);
                    }
                }
            }
        }

2 Likes

Wow, thanks a lot! Just what I was looking for and it works like a charm! This should be implemented in the next version!

Here is an update to the modification that also takes scaling into account. Aron, I am hoping that this or a similar fix can be included in a future update. Thanks.

void CollectTreeMeshes(Terrain terrain, List<RasterizationMesh> result)
        {
            // modified
            TerrainData data = terrain.terrainData;
            Vector3 treeColliderPos;
            Quaternion treeColliderRot;

            for (int i = 0; i < data.treeInstances.Length; i++) {
                TreeInstance instance = data.treeInstances[i];
                TreePrototype prot = data.treePrototypes[instance.prototypeIndex];
                // Make sure that the tree prefab exists
                if (prot.prefab == null) {
                    continue;
                }
                Collider[] treeColliders = prot.prefab.transform.GetComponentsInChildren<Collider>();
                var treePosition = terrain.transform.position + Vector3.Scale(instance.position, data.size);
                var scale = new Vector3(instance.widthScale * prot.prefab.transform.localScale.x, instance.heightScale * prot.prefab.transform.localScale.y, instance.widthScale * prot.prefab.transform.localScale.z);
                for (int x = 0; x < treeColliders.Length; x++) {
                    treeColliderPos = treePosition + Vector3.Scale(treeColliders[x].gameObject.transform.position - prot.prefab.transform.position, scale);
                    treeColliderRot = treeColliders[x].gameObject.transform.rotation;
                    // Generate a mesh from the collider
                    RasterizationMesh mesh = RasterizeCollider(treeColliders[x], Matrix4x4.TRS(treeColliderPos, treeColliderRot, scale));
                    // Make sure a valid mesh was generated
                    if (mesh != null) {
                        // The bounds are incorrectly based on collider.bounds.
                        // It is incorrect because the collider is on the prefab, not on the tree instance
                        // so we need to recalculate the bounds based on the actual vertex positions
                        mesh.RecalculateBounds();
                        result.Add(mesh);
                    }
                }
            }
        }