The problem: Removing a tree’s instance data from the terrain TreeInstances doesn’t remove the tree’s collider. So while the tree may go…you keep bumping into its invisible collider.
I had to do this:
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
to force the terrain to regenerate.
The gotcha (always gonna be one) is, large number of trees or details will cause a hitch. With 10000 trees alone, it caused a .25 second hitch in gameplay.
Luckily (for me), my terrains are relatively small, just for this type of thing. I tile them, so changes to any one terrain aren’t going to matter much. 2000 trees is barely noticeable.
Hope this helps others. If it is useful to you, I’d appreciate it if someone could post an even faster search algorithm…maybe a quadtree (yuk yuk) or something. The linear search probably isn’t the fastest. To be honest though…it is a VERY small amount of the time. The terrain regen is the big part.
Here is the code:
public class TreeChop : MonoBehaviour
{
public GameObject FallingTreePrefab;
private List<TreeInstance> TreeInstances;
// Use this for initialization
private void Start()
{
TreeInstances = new List<TreeInstance>(Terrain.activeTerrain.terrainData.treeInstances);
Debug.Log("Tree Instances:" + TreeInstances.Count);
}
// Update is called once per frame
private void Update()
{
// did we click on a tree?
if (Input.GetMouseButtonDown(0))
{
DateTime start = DateTime.Now;
RaycastHit hit;
// This ray will see where we clicked er chopped
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Did we hit anything at that point, out as far as 10 units?
if (Physics.Raycast(ray, out hit, 10.0f))
{
// Did we even click er chop on the terrain/tree?
if (hit.collider.name != Terrain.activeTerrain.name)
{
// No, must have been a frantic attack on a giant spider >:)
return;
}
// We hit the "terrain"! Now, how high is the ground at that point?
float sampleHeight = Terrain.activeTerrain.SampleHeight(hit.point);
// If the height of the exact point we clicked/chopped at or below ground level, all we did
// was chop dirt.
if (hit.point.y <= sampleHeight + 0.01f)
{
return;
}
TerrainData terrain = Terrain.activeTerrain.terrainData;
TreeInstance[] treeInstances = terrain.treeInstances;
// Our current closest tree initializes to far away
float maxDistance = float.MaxValue;
// Track our closest tree's position
Vector3 closestTreePosition = new Vector3();
// Let's find the closest tree to the place we chopped and hit something
int closestTreeIndex = 0;
for (int i = 0; i < treeInstances.Length; i++)
{
TreeInstance currentTree = treeInstances[i];
// The the actual world position of the current tree we are checking
Vector3 currentTreeWorldPosition = Vector3.Scale(currentTree.position, terrain.size) + Terrain.activeTerrain.transform.position;
// Find the distance between the current tree and whatever we hit when chopping
float distance = Vector3.Distance(currentTreeWorldPosition, hit.point);
// Is this tree even closer?
if (distance < maxDistance)
{
maxDistance = distance;
closestTreeIndex = i;
closestTreePosition = currentTreeWorldPosition;
}
}
// Remove the tree from the terrain tree list
TreeInstances.RemoveAt(closestTreeIndex);
terrain.treeInstances = TreeInstances.ToArray();
// Now refresh the terrain, getting rid of the darn collider
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
// Put a falling tree in its place
Instantiate(FallingTreePrefab, closestTreePosition, Quaternion.identity);
Debug.Log(DateTime.Now - start);
}
}
}
}