TerrainData Memory Issues

I’ve created an editor script that divides a terrain into a grid of smaller terrains.

The script is complete and works fine except that when I divide a terrain into lots of new terrains (eg. > 200), the memory usage gets too high and eventually Unity crashes.

I’m fairly certain this is caused by the number of TerrainData assets being created and not by memory leaks in my loop. If I create the same pieces of the terrain multiple times the memory usage does not increase.

I’ll post code below that is pretty much just the TerrainData creation stuff. There’s probably a lot of things that don’t need to be in there and are just from my trying to solve this.

As soon as I delete the TerrainData assets that are created from the project, the Unity memory usage drops back down to a normal level. Is this just an issue of Unity using lots of memory for each TerrainData in the project? Adding multiple terrains normally from within Unity also bumps up the memory usage until the TerrainData assets are deleted.

using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections;
using System.Threading;
using System.Text;

public class TerrainMemoryTest : EditorWindow
{
	enum ObjFormat {Triangles, Quads}
	enum ObjResolution {Full, Half, Quarter, Eighth, Sixteenth}

	int					newChunkSize = 100;
	int					chunkSize = 0;
	TerrainData			terrain = null;
	Vector3				terrainPos = Vector3.zero;
	Terrain				terrainObject = null;
	int					terrainSize = 0;
	int					numChunksX = 0;
	int					numChunksY = 0;
	int					chunkSplatMap = 64;
	int					chunkHeightMap = 64;
	string				sceneLocation = Application.dataPath + "/TerrainScenes/";
	
	int					processFromChunkX = 0;
	int					processFromChunkY = 0;
	int					processToChunkX = 0;
	int					processToChunkY = 0;

	[MenuItem ("Terrain/Export Terrain Test")]
	static void Init()
    {
		((TerrainMemoryTest)EditorWindow.GetWindow (typeof (TerrainMemoryTest))).Show();
	}
	
	void OnGUI ()
	{
		bool allowed = true;
		
		EditorGUILayout.Space();
		
		terrainObject = Selection.activeObject as Terrain;
        if (!terrainObject)
		{
            terrainObject = Terrain.activeTerrain;
        }
        if (terrainObject)
		{
            terrain = terrainObject.terrainData;
            terrainPos = terrainObject.transform.position;
        }
		
        if (!terrain)
		{
            GUILayout.Label("No terrain found");
			
            if (GUILayout.Button("Cancel"))
			{
				((TerrainMemoryTest)EditorWindow.GetWindow(typeof (TerrainMemoryTest))).Close();
            }
            return;
        }
		
		terrainSize = Mathf.RoundToInt( terrain.size.x );
		
		newChunkSize = EditorGUILayout.IntField( "Chunk (C) Size", newChunkSize );
		
		/*if ( newChunkSize.ToString().Length > terrainSize.ToString().Length )
		{
			newChunkSize = int.Parse( newChunkSize.ToString().Substring( 0, terrainSize.ToString().Length ) );
		}*/
		
		if ( newChunkSize > terrainSize ) newChunkSize = terrainSize;
		if ( newChunkSize != chunkSize )
		{
			if ( newChunkSize > terrain.size.x ) newChunkSize = Mathf.RoundToInt(terrain.size.x);
			ChangeChunkSize( newChunkSize );
			
			numChunksX = Mathf.CeilToInt( terrain.size.x / chunkSize );
			numChunksY = Mathf.CeilToInt( terrain.size.z / chunkSize );
			
			processFromChunkX = 1;
			processFromChunkY = 1;
			
			processToChunkX = numChunksX;
			processToChunkY = numChunksY;
		}
		
		int splatRes = terrain.alphamapResolution;
		int heightRes = terrain.heightmapResolution;
		
		chunkSplatMap = Mathf.RoundToInt( splatRes / numChunksX );
		chunkHeightMap = Mathf.RoundToInt( (heightRes-1) / numChunksX );
		
		if ( numChunksX != 2  numChunksX != 4  numChunksX != 8  numChunksX != 16  numChunksX != 32  numChunksX != 64  numChunksX != 128  numChunksX != 256  numChunksX != 512  numChunksX != 1024  numChunksX != 2048  numChunksX != 4096 )
		{
			allowed = false;
			
			EditorGUILayout.LabelField( "Error: ", "Number of chunks must be a power of 2" );
		}
		
		EditorGUILayout.LabelField( "Terrain Size:", terrain.size.x + "x" + terrain.size.z );
		EditorGUILayout.LabelField( "SplatMap Res:", splatRes + "x" + splatRes );
		EditorGUILayout.LabelField( "HeightMap Res:", heightRes + "x" + heightRes );
		EditorGUILayout.LabelField( "Num Chunks:", numChunksX + "x" + numChunksY );
		EditorGUILayout.LabelField( "C SplatMap Res:", chunkSplatMap + "x" + chunkSplatMap );
		EditorGUILayout.LabelField( "C HeightMap Res:", chunkHeightMap + "x" + chunkHeightMap );
		
		EditorGUILayout.Separator();
		
		processFromChunkX = EditorGUILayout.IntField( "From Chunk X", processFromChunkX );
		processFromChunkY = EditorGUILayout.IntField( "From Chunk Y", processFromChunkY );
		processToChunkX = EditorGUILayout.IntField( "To Chunk Y", processToChunkX );
		processToChunkY = EditorGUILayout.IntField( "To Chunk Y", processToChunkY );
		
		if ( processFromChunkX < 1 ) processFromChunkX = 1;
		if ( processFromChunkY < 1 ) processFromChunkY = 1;
		if ( processToChunkX < 1 ) processToChunkX = 1;
		if ( processToChunkY < 1 ) processToChunkY = 1;
		if ( processFromChunkX > processToChunkX ) processFromChunkX = processToChunkX;
		if ( processFromChunkY > processToChunkY ) processFromChunkY = processToChunkY;
		if ( newChunkSize > terrain.size.x ) newChunkSize = Mathf.RoundToInt(terrain.size.x);
		if ( processToChunkX > numChunksX ) processToChunkX = numChunksX;
		if ( processToChunkY > numChunksY ) processToChunkY = numChunksY;
		if ( processToChunkX < processFromChunkX ) processToChunkX = processFromChunkX;
		if ( processToChunkY < processFromChunkY ) processToChunkY = processFromChunkY;
		
		EditorGUILayout.Separator();
		EditorGUILayout.Space();
		
		if( allowed )
		{
			if (GUILayout.Button("Run Process"))
			{
				Export();
				
				((TerrainMemoryTest)EditorWindow.GetWindow(typeof (TerrainMemoryTest))).Close();
			}
		}
		
		terrainObject = null;
    }
	
	void ChangeChunkSize ( int newChunkSize )
	{
		if ( newChunkSize > terrain.size.x ) newChunkSize = Mathf.RoundToInt(terrain.size.x);
		chunkSize = newChunkSize;
	}
	
	void Export ()
	{
		//int numLayers = terrain.alphamapLayers;

		//Color[][] textureColours = new Color[4][];
		
		int numberOfProcedures = 1;
		
		int progressMax = (processToChunkX - processFromChunkX + 1) * (processToChunkY - processFromChunkY + 1) * numberOfProcedures;
		int currentProgress = -1;
		
		UpdateProgress( currentProgress++, progressMax, "Starting..." );
		
		// Run through each chunk
		for ( int cX = processFromChunkX-1; cX < processToChunkX; cX++ )
		{
			for ( int cY = processFromChunkY-1; cY < processToChunkY; cY++ )
			{
				//int[] usedLayerMaps = new int[4]{-1,-1,-1,-1};
				//int numLayersUsed = 0;
				//string dirName = cX + "-" + cY + "/";
				//string filename = "";
				if ( System.IO.Directory.Exists ( sceneLocation + "TerrainData" ) )
				{
					//System.IO.Directory.Delete ( sceneLocation, true );
					Thread.Sleep( 5 );
				}
				System.IO.Directory.CreateDirectory( sceneLocation + "TerrainData" );
				
				TerrainData terrainChunkData = null;
				
				string assetDir = "Assets/TerrainChunkData";
				//UnityEngine.Object tData = AssetDatabase.LoadAssetAtPath( assetDir + "/TCD.asset", typeof( TerrainData ) );
				//terrainChunkData = Instantiate ( tData) as TerrainData;
				//terrainChunkData.heightmapResolution = chunkHeightMap;
				//terrainChunkData.alphamapResolution = chunkSplatMap;
				//terrainChunkData.size = new Vector3( chunkSize, terrain.size.y, chunkSize );
				AssetDatabase.DeleteAsset( "Assets" + sceneLocation.Replace( Application.dataPath, "" ) + "TerrainData/TerrainChunk_" + cX + "-" + cY + ".asset" );
				Thread.Sleep ( 500 );
				AssetDatabase.CopyAsset( assetDir + "/TCD.asset", "Assets" + sceneLocation.Replace( Application.dataPath, "" ) + "TerrainData/TerrainChunk_" + cX + "-" + cY + ".asset" );
				Thread.Sleep ( 500 );
				AssetDatabase.Refresh();
				Thread.Sleep ( 50 );
				//DestroyImmediate ( terrainChunkData );
				//terrainChunkData = null;
				terrainChunkData = AssetDatabase.LoadAssetAtPath( "Assets" + sceneLocation.Replace( Application.dataPath, "" ) + "TerrainData/TerrainChunk_" + cX + "-" + cY + ".asset", typeof( TerrainData ) ) as TerrainData;
				terrainChunkData.heightmapResolution = chunkHeightMap;
				terrainChunkData.alphamapResolution = chunkSplatMap;
				terrainChunkData.size = new Vector3( chunkSize, terrain.size.y, chunkSize );
				//tData = null;

				// Create Terrain Chunk
				GameObject terrainChunk = Terrain.CreateTerrainGameObject( terrainChunkData );
				terrainChunk.name = "TerrainChunk_" + cX + "-" + cY;
				terrainChunk.transform.position = new Vector3( terrainPos.x + cX * chunkSize, terrainPos.y, terrainPos.z + cY * chunkSize );

				terrainChunkData = null;
				
				Thread.Sleep( 10 );
				
				System.GC.Collect();
				
				UpdateProgress( currentProgress++, progressMax, cX + "x" + cY + " -- " + "Done..." );
				
				AssetDatabase.SaveAssets();
				
				Thread.Sleep( 20 );
			}
		}
		
		terrain = null;

		AssetDatabase.Refresh();
		
		EditorUtility.ClearProgressBar();
	}
	
	void UpdateProgress ( int progress, int progressMax, string progressDescription )
	{
		EditorUtility.DisplayProgressBar( "Processing...", progressDescription, progress*1f / progressMax  );
    }
	
	Texture2D TexScale ( Texture2D tex, int xb, int yb )
	{
		Texture2D ret = new Texture2D( xb, yb, tex.format, false );
		ret.wrapMode = TextureWrapMode.Clamp;
		for ( int x = 0; x < xb; x++ )
		{
			for ( int y = 0; y < yb; y++ )
			{
				float xp = ( (x*1f) / ((xb-1)*1f) );//x Percentage
				float yp = ( (y*1f) / ((yb-1)*1f) );//y Percentage
				//float xo = ( xp * (tex.width-1) );//Other X pos
				//float yo = ( yp * (tex.height-1) );//Other Y pos
				ret.SetPixel( x, y, tex.GetPixelBilinear( xp, yp ) );
			}
		}
		ret.Apply();
		return ret;
	}
	
	bool HasUsedAllLayers ( int[] usedLayers )
	{
		for ( int i = 0; i < usedLayers.Length; i++ )
		{
			if ( usedLayers[i] == -1 )
			{
				return false;
			}
		}
		
		return true;
	}
	
	void AddUsedLayer ( int[] usedLayers, int layerId )
	{
		for ( int i = 0; i < usedLayers.Length; i++ )
		{
			if ( usedLayers[i] == -1 )
			{
				usedLayers[i] = layerId;
				return;
			}
		}
	}
	
	bool HasUsedLayer ( int[] usedLayers, int layerId )
	{
		for ( int i = 0; i < usedLayers.Length; i++ )
		{
			if ( usedLayers[i] == layerId )
			{
				return true;
			}
		}
		
		return false;
	}
	
	bool HasUsedLayer ( ArrayList usedLayers, int layerId )
	{
		for ( int i = 0; i < usedLayers.Count; i++ )
		{
			if ( (int)usedLayers[i] == layerId )
			{
				return true;
			}
		}
		
		return false;
	}
	
	float GetColourFromTexture ( Texture2D tex, int x, int y, int colourId )
	{
		if ( colourId == 0 )
		{
			return tex.GetPixel( x, y ).r;
		}
		else if ( colourId == 1 )
		{
			return tex.GetPixel( x, y ).g;
		}
		else if ( colourId == 2 )
		{
			return tex.GetPixel( x, y ).b;
		}
		else if ( colourId == 3 )
		{
			return tex.GetPixel( x, y ).a;
		}
		
		return 0f;
	}
}

253255–9114–$terrainmemorytest_158.cs (9.74 KB)

From further searching this appears to be the same issue pointed out here: http://forum.unity3d.com/viewtopic.php?t=24313.

There was no response to that. Hopefully there’s some sort of solution to this.

I spoke to rom (who had the same issue in the other thread I linked to) about this and his suggestion of running AssetDatabase.ImportAsset() on the newly created terraindata object fixed my issue.

Just wanted to post this solution in case anyone else had the same problem.

Hey it’s been awhile sense this post came up, any results with this? I’m doing the same thing only by hand and I also notice unity using an increased amount of memory, I assumed it was just the amount of terrains I’m using but after reading this maybe there is something that can be done?