Hello, I am creating this post to help any future wanderers encountering unreasonably big TerrainData files (likely due to some untracked data or corruption). Here is a step by step guide on how to recreate the terrainData while keeping all settings.
TerrainData keeps track of terrain geometry and details/trees placement.
-
Saving Heightmap/Splatmap
Unity has a handy Import/Export tool for those in TerrainToolbox.
(Window/Terrain/Terrain Toolbox/Terrain Utilities)
-
Create new TerrainData file with same size (width, height…) as the previous one. You can do this manually by creating a new terrain or use TerrainTools again which is a little more convenient (Note we dont want to Duplicate the TerrainData from old terrain as that will also copy the problematic inflated data).
You can locate the new TerrainData file through the TerrainCollider component, its usually Assets/Terrain/ -
Update old Terrain Object with new TerrainData
Turn on debug mode in Inspector (Three dots/Tick Debug). Now a new field in the terrain component will show up:
Place the new terrainData in it as well as in the TerrainCollider. -
Reimport Splats/Heightmaps
This can be done in terrainToolbox again using Terrain Utilities/Import. -
Import Details/Trees
I havent seen a way to do this with TerrainTools so here is an editor script which copies Coverage Grass + Trees placement data from old TerrainData to a new one:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class TerrainCopyExtension : EditorWindow
{
private TerrainData sourceTerrainData;
private TerrainData destinationTerrainData;
private bool copyTrees = true;
private bool copyGrass = true;
[MenuItem("Tools/Terrain Copy Extension")]
public static void ShowWindow()
{
GetWindow<TerrainCopyExtension>("Terrain Copy");
}
private void OnGUI()
{
GUILayout.Label("Copy Terrain Data", EditorStyles.boldLabel);
sourceTerrainData = (TerrainData)EditorGUILayout.ObjectField("Source Terrain Data", sourceTerrainData, typeof(TerrainData), false);
destinationTerrainData = (TerrainData)EditorGUILayout.ObjectField("Destination Terrain Data", destinationTerrainData, typeof(TerrainData), false);
copyTrees = EditorGUILayout.Toggle("Copy Trees", copyTrees);
copyGrass = EditorGUILayout.Toggle("Copy Grass", copyGrass);
if (GUILayout.Button("Copy Data"))
{
if (sourceTerrainData == null || destinationTerrainData == null)
{
EditorUtility.DisplayDialog("Error", "Please assign both source and destination Terrain Data.", "OK");
return;
}
CopyTerrainData();
}
}
private void CopyTerrainData()
{
if (copyTrees)
{
CopyTrees();
}
if (copyGrass)
{
CopyGrass();
}
EditorUtility.SetDirty(destinationTerrainData);
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("Success", "Terrain data copied successfully!", "OK");
}
private void CopyTrees()
{
// Copy tree prototypes
destinationTerrainData.treePrototypes = sourceTerrainData.treePrototypes;
// Get the source tree instances
TreeInstance[] sourceTreeInstances = sourceTerrainData.treeInstances;
// Create a list to store the new tree instances
List<TreeInstance> newTreeInstances = new List<TreeInstance>();
// Calculate the scale factors
Vector3 sourceSize = sourceTerrainData.size;
Vector3 destSize = destinationTerrainData.size;
Vector3 scaleFactor = new Vector3(destSize.x / sourceSize.x, destSize.y / sourceSize.y, destSize.z / sourceSize.z);
// Copy and adjust each tree instance
foreach (TreeInstance sourceTree in sourceTreeInstances)
{
TreeInstance newTree = sourceTree;
// Adjust the position to fit the new terrain
newTree.position = new Vector3(
sourceTree.position.x * scaleFactor.x,
sourceTree.position.y * scaleFactor.y,
sourceTree.position.z * scaleFactor.z
);
// Optionally, you might want to adjust the scale of the trees as well
newTree.widthScale *= scaleFactor.x;
newTree.heightScale *= scaleFactor.y;
newTreeInstances.Add(newTree);
}
// Set the new tree instances on the destination terrain
destinationTerrainData.SetTreeInstances(newTreeInstances.ToArray(), true);
}
private void CopyGrass()
{
int layerCount = sourceTerrainData.detailPrototypes.Length;
destinationTerrainData.detailPrototypes = sourceTerrainData.detailPrototypes;
for (int layer = 0; layer < layerCount; layer++)
{
int[,] sourceDetailLayer = sourceTerrainData.GetDetailLayer(0, 0, sourceTerrainData.detailWidth, sourceTerrainData.detailHeight, layer);
destinationTerrainData.SetDetailLayer(0, 0, layer, sourceDetailLayer);
}
}
}
And that should be all to get a 1:1 terrain copy onto new TerrainData. With these steps I’ve reduced TerrainData from 1.1Gb all the way down to 143mb. If there is a better way to do it. feel free to contribute to the thread!