[SOLVED] Unity 5 - Runtime Terrain Deformation Collider Bug

It seems that to properly update a terrain and its collider in Unity 5 you have to save the scene. Similar to this issue:

The problem is that we’re trying to do runtime deformation of the terrain and it properly updates it visually, but the colliders do not change. We set the terrain to not be static, but that had no effect.

Is there something that needs to be triggered programmatically on the terrain or collider for it to update from the TerrainData or is changing a TerrainCollider at runtime no longer supported?

[EDIT:
This appears to be fixed in Unity 5.0.1f1]

Found a work around for anyone trying to generate the terrain once at runtime using CreateTerrainGameObject.

GameObject terrainObj = Terrain.CreateTerrainGameObject( terrainData );
terrainObj.transform.position = transform.position;
terrainObj.transform.rotation = transform.rotation;
	
float [,] heights = terrainData.GetHeights( 0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution );
terrainData.SetHeights( 0, 0, heights );

This prevented the physics geo from being mirrored around x/z.

I’ve submitted a bug report here:
http://fogbugz.unity3d.com/default.asp?683393_bdjig2ks7rjle98v

Also here’s the code I used for the bug report if anyone wants to try it:

public class OnClickRaiseTerrain : MonoBehaviour
{
	void Awake()
	{
		Cursor.visible = false;
		Cursor.lockState = CursorLockMode.Locked;

		// Create a new instance of the Terrain Data to not edit in the project
		foreach( Terrain terrain in Terrain.activeTerrains )
		{
			TerrainData inst = (TerrainData) Object.Instantiate( terrain.terrainData );
			terrain.terrainData = inst;
		}
	}

	void Update()
	{
		if( Input.GetMouseButtonDown( 0 ) )
		{
			RaycastHit hitInfo;

			if( Physics.Raycast( Camera.main.ScreenPointToRay( Camera.main.pixelRect.center ), out hitInfo ) )
			{
				Terrain terrain = hitInfo.collider.GetComponent<Terrain>();
				if( terrain )
				{
					float[,] heights = new float[,]
					{
						{ .25f,	.5f, .25f },
						{ .5f,	1f,	.25f },
						{ .25f,	.5f, .25f },
					};

					int x, y;
					WorldToTerrainAlphaMap( terrain, hitInfo.point, out x, out y );
					terrain.terrainData.SetHeights( x - 1, y - 1, heights );
				}
			}
		}
	}

	private static void WorldToTerrainAlphaMap( Terrain terrain, Vector3 position, out int alphaMap_x, out int alphaMap_y )
	{
		alphaMap_x = (int) (((position.x - terrain.GetPosition().x) / terrain.terrainData.size.x) * terrain.terrainData.alphamapWidth);
		alphaMap_y = (int) (((position.z - terrain.GetPosition().z) / terrain.terrainData.size.z) * terrain.terrainData.alphamapHeight);
	}
}

more of a comment than an answer but the answer from a.lomo works well. i could not find the right place to put the code in the bug report code as listed above but i did apply it to a script i found at Modifying terrain height under a gameobject at runtime - Unity Answers the answer from Jari Kaiji is the specific one. in that script they already have heights as a float so all you have to put in is the

terrain.terrainData.GetHeights( 0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution );
terrain.terrainData.SetHeights( 0, 0, heights );

and i placed that after the alpha map update around line 172. i also changed the terrain to myTerrain since its a float at the top (makes it easy so whatever your terrain name is it gets attached to the myTerrain calls and that is the way it is done in the code to keep it consistent. just as a warning though if you use any other terrain modifying script as a test you will need to place the terrain back in the myterrain in the terrain inspector. below is that code modified. as stated in the other article place this on the terrain. make sure to click the test with mouse.

using UnityEngine;
using System.Collections;

public class RaiseLowerTerrain : MonoBehaviour
{
	public bool TestWithMouse = false;
	public Terrain myTerrain;
	public int SmoothArea;
	private int xResolution;
	private int zResolution;
	private float[,] heights;
	private float[,] heightMapBackup;
	protected const float DEPTH_METER_CONVERT=0.05f;
	protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
	public int DeformationTextureNum = 1;
	protected int alphaMapWidth;
	protected int alphaMapHeight;
	protected int numOfAlphaLayers;
	private float[, ,] alphaMapBackup;

	
	
	void Start()
	{
		xResolution = myTerrain.terrainData.heightmapWidth;
		zResolution = myTerrain.terrainData.heightmapHeight;
		alphaMapWidth = myTerrain.terrainData.alphamapWidth;
		alphaMapHeight = myTerrain.terrainData.alphamapHeight;
		numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;
		
		if (Debug.isDebugBuild)
		{
			heights = myTerrain.terrainData.GetHeights (0, 0, xResolution, zResolution);    
			heightMapBackup = myTerrain.terrainData.GetHeights(0, 0, xResolution, zResolution);
			alphaMapBackup = myTerrain.terrainData.GetAlphamaps(0, 0, alphaMapWidth, alphaMapHeight);   
		}
		
	}
	
	void OnApplicationQuit()
	{
		if (Debug.isDebugBuild)
		{
			myTerrain.terrainData.SetHeights(0, 0, heightMapBackup);
			myTerrain.terrainData.SetAlphamaps(0, 0, alphaMapBackup);
		}
	}
	
	
	void Update()
	{
		// This is just for testing with mouse!
		// Point mouse to the Terrain. Left mouse button
		// raises and right mouse button lowers terrain.
		if (TestWithMouse == true) 
		{
			if (Input.GetMouseButtonDown (0)) 
			{
				RaycastHit hit;
				Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				if (Physics.Raycast (ray, out hit)) 
				{
					// area middle point x and z, area width, area height, smoothing distance, area height adjust
					raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, -0.01f); 
					// area middle point x and z, area size, texture ID from terrain textures
					TextureDeformation (hit.point, 10 * 2f, DeformationTextureNum);
				}
			}
			if (Input.GetMouseButtonDown (1)) 
			{
				RaycastHit hit;
				Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				if (Physics.Raycast (ray, out hit)) 
				{
					// area middle point x and z, area width, area height, smoothing distance, area height adjust
					raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, 0.01f);
					// area middle point x and z, area size, texture ID from terrain textures
					TextureDeformation (hit.point, 10 * 2f, 0);
				}
			}
		}
	}
	
	
	private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
	{
		int areax;
		int areaz;
		smooth += 1;
		float smoothing;
		int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
		int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
		lenx += smooth;
		lenz += smooth;
		terX -= (lenx / 2);
		terZ -= (lenz / 2);
		if (terX < 0) terX = 0;
		if (terX > xResolution)    terX = xResolution;
		if (terZ < 0) terZ = 0;
		if (terZ > zResolution)    terZ = zResolution;
		float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
		float y = heights[lenx/2,lenz/2];
		y += incdec;
		for (smoothing=1; smoothing < smooth+1; smoothing++) 
		{
			float multiplier = smoothing / smooth;
			for (areax = (int)(smoothing/2); areax < lenx-(smoothing/2); areax++) 
			{
				for (areaz = (int)(smoothing/2); areaz < lenz-(smoothing/2); areaz++) 
				{
					if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution)) 
					{
						heights [areax, areaz] = Mathf.Clamp((float)y*multiplier,0,1);
					}
				}
			}
		}

		myTerrain.terrainData.SetHeights (terX, terZ, heights);
	}
	
	private void raiselowerTerrainPoint(Vector3 point, float incdec)
	{
		int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
		int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
		float y = heights[terX,terZ];
		y += incdec;
		float[,] height = new float[1,1];
		height[0,0] = Mathf.Clamp(y,0,1);
		heights[terX,terZ] = Mathf.Clamp(y,0,1);
		myTerrain.terrainData.SetHeights(terX, terZ, height);
	}
	
	protected void TextureDeformation(Vector3 pos, float craterSizeInMeters,int textureIDnum)
	{
		Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
		int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
		int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
		int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
		int alphaMapStartPosZ = (int)(alphaMapTerrainPos.z - (alphaMapCraterLength/2));
		float[, ,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
		float circlePosX;
		float circlePosY;
		float distanceFromCenter;
		for (int i = 0; i < alphaMapCraterLength; i++) //width
		{
			for (int j = 0; j < alphaMapCraterWidth; j++) //height
			{
				circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
				circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
				distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
				if (distanceFromCenter < (craterSizeInMeters / 2.0f))
				{
					for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++)
					{
						//could add blending here in the future
						if (layerCount == textureIDnum)
						{
							alphas[i, j, layerCount] = 1;
						}
						else
						{
							alphas[i, j, layerCount] = 0;
						}
					}
				}
			}
		} 
		myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);

		// the next two lines are added to update the terrain to fix the unity 5 terrain collidor not updating
		heights = myTerrain.terrainData.GetHeights( 0, 0, myTerrain.terrainData.heightmapResolution, myTerrain.terrainData.heightmapResolution );
		myTerrain.terrainData.SetHeights( 0, 0, heights );
	}
	
	protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain)
	{
		Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
		Vector3 coord;
		coord.x = tempCoord.x / myTerrain.terrainData.size.x;
		coord.y = tempCoord.y / myTerrain.terrainData.size.y;
		coord.z = tempCoord.z / myTerrain.terrainData.size.z;
		return coord;
	}
	
	protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos,Terrain terrain, int mapWidth, int mapHeight)
	{
		Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
		return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
	} 
}

there is another piece of code at Gradual Terrain Smoothing from a Flat Area (at Runtime) - Unity Answers that i would like to see if i can add to this and hopefully smooth the terrain more than the square you get from the first script. another thing i am still trying to find is a script to get the gameobjects dimensions to make the actual raise and lowering of terrain (my use is for flattening terrain when placing but also could be for basic primitive sculpting for a game also.

For anybody looking for help, in v 2017, 2018… to update the TerrainCollider you have to update the heights on the terraindata of both the terrainCollider and the terrain. Seems like there is a copy. Changing the terrain only will update the visualization but not the collider itself.

terrainCollider.terrainData.setHeights(...)
terrain.terrainData.setHeights(...)

In v2019 the terrain has been moved to GPU and the API changed, so maybe this doesn’t work. Seems like there is a function to update “dirty” data.