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)