568 lines
26 KiB
C#
568 lines
26 KiB
C#
|
using System;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using NUnit.Framework;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.TerrainTools;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
namespace UnityEditor.TerrainTools
|
||
|
{
|
||
|
class RegressionTests
|
||
|
{
|
||
|
GameObject m_TerrainGO;
|
||
|
Terrain m_TerrainComponent;
|
||
|
private int m_NumTerrains;
|
||
|
|
||
|
[Test]
|
||
|
[TestCase(0f, 1f, Heightmap.Format.PNG, Ignore = "Failing on Ubuntu")]
|
||
|
[TestCase(.25f, .75f, Heightmap.Format.PNG, Ignore = "Failing on Ubuntu")]
|
||
|
[TestCase(0f, 1f, Heightmap.Format.TGA, Ignore = "Failing on Ubuntu")]
|
||
|
[TestCase(.25f, .75f, Heightmap.Format.TGA, Ignore = "Failing on Ubuntu")]
|
||
|
[TestCase(0f, 1f, Heightmap.Format.RAW)]
|
||
|
[TestCase(.25f, .75f, Heightmap.Format.RAW)]
|
||
|
[TestCase(0f, 1f, Heightmap.Format.RAW, Heightmap.Depth.Bit8)]
|
||
|
[TestCase(.25f, .75f, Heightmap.Format.RAW, Heightmap.Depth.Bit8)]
|
||
|
public void TerrainToolboxUtilites_WhenExportHeightmap_LevelCorrectionWorks(float min, float max, Heightmap.Format format, Heightmap.Depth depth = Heightmap.Depth.Bit16)
|
||
|
{
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
Texture2D gradientTexture = CreateGradientTexture();
|
||
|
|
||
|
int heightmapResolution = 513;
|
||
|
int numberOfTiles = 1;
|
||
|
int baseLevel = 0;
|
||
|
int remapLevel = 1;
|
||
|
ToolboxHelper.CopyTextureToTerrainHeight(m_TerrainComponent.terrainData, gradientTexture, Vector2Int.zero, heightmapResolution, numberOfTiles, baseLevel, remapLevel);
|
||
|
|
||
|
Selection.activeGameObject = m_TerrainGO;
|
||
|
m_TerrainGO.name = "TestTerrain";
|
||
|
m_TerrainComponent.name = "TestComponent";
|
||
|
|
||
|
RenderTexture oldRT = RenderTexture.active;
|
||
|
RenderTexture.active = m_TerrainComponent.terrainData.heightmapTexture;
|
||
|
|
||
|
//Run Tests and Cleanup files
|
||
|
string fileName = m_TerrainGO.name + "_heightmap";
|
||
|
string path = Path.Combine(toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightmapFolderPath, fileName);
|
||
|
switch (format)
|
||
|
{
|
||
|
case Heightmap.Format.PNG:
|
||
|
path += ".png";
|
||
|
Assert.IsTrue(TestLevelCorrection(toolboxWindow, new Vector2(min, max), path, format));
|
||
|
FileUtil.DeleteFileOrDirectory(path);
|
||
|
FileUtil.DeleteFileOrDirectory(path + ".meta");
|
||
|
break;
|
||
|
case Heightmap.Format.TGA:
|
||
|
path += ".tga";
|
||
|
Assert.IsTrue(TestLevelCorrection(toolboxWindow, new Vector2(min, max), path, format));
|
||
|
FileUtil.DeleteFileOrDirectory(path);
|
||
|
FileUtil.DeleteFileOrDirectory(path + ".meta");
|
||
|
break;
|
||
|
case Heightmap.Format.RAW:
|
||
|
path += ".raw";
|
||
|
Assert.IsTrue(TestLevelCorrection(toolboxWindow, new Vector2(min, max), path, depth));
|
||
|
FileUtil.DeleteFileOrDirectory(path);
|
||
|
FileUtil.DeleteFileOrDirectory(path + ".meta");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
AssetDatabase.Refresh();
|
||
|
RenderTexture.active = oldRT;
|
||
|
toolboxWindow.Close();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This overloaded method deals specifically with testing the level correction of the raw format
|
||
|
/// </summary>
|
||
|
/// <param name="toolboxWindow">Window where the Export Heightmap Utilities live</param>
|
||
|
/// <param name="minMaxRemap">Min and Max values of the remap</param>
|
||
|
/// <param name="format">Heightmap File Format</param>
|
||
|
/// <param name="path">String path of the files directory location</param>
|
||
|
/// <param name="depth">Heightmap Bit Depth</param>
|
||
|
/// <returns></returns>
|
||
|
bool TestLevelCorrection(TerrainToolboxWindow toolboxWindow, Vector2 minMaxRemap, string path, Heightmap.Depth depth)
|
||
|
{
|
||
|
//Execute the repro steps in code
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.ExportHeightRemapMin = minMaxRemap.x;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.ExportHeightRemapMax = minMaxRemap.y;
|
||
|
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightFormat = Heightmap.Format.RAW;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightmapDepth = depth;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_SelectedDepth = (depth == Heightmap.Depth.Bit16) ? 0 : 1;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ExportHeightmaps(new Object[] { m_TerrainComponent });
|
||
|
|
||
|
//Get byte data of the terrain's heightmap
|
||
|
TerrainData terrainData = m_TerrainComponent.terrainData;
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
int heightmapWidth = terrainData.heightmapResolution - 1;
|
||
|
int heightmapHeight = terrainData.heightmapResolution - 1;
|
||
|
#else
|
||
|
int heightmapWidth = terrainData.heightmapWidth - 1;
|
||
|
int heightmapHeight = terrainData.heightmapHeight - 1;
|
||
|
#endif
|
||
|
float[,] heights = terrainData.GetHeights(0, 0, heightmapWidth, heightmapHeight);
|
||
|
byte[] data = new byte[heightmapWidth * heightmapHeight * (int)depth];
|
||
|
|
||
|
if (depth == Heightmap.Depth.Bit16)
|
||
|
{
|
||
|
float normalize = (1 << 16);
|
||
|
for (int y = 0; y < heightmapHeight; ++y)
|
||
|
{
|
||
|
for (int x = 0; x < heightmapWidth; ++x)
|
||
|
{
|
||
|
//Remapping the heightmap data
|
||
|
int index = x + y * heightmapWidth;
|
||
|
float remappedHeight = heights[y, x] * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
|
||
|
int height = Mathf.RoundToInt(remappedHeight * normalize);
|
||
|
ushort compressedHeight = (ushort)Mathf.Clamp(height, 0, ushort.MaxValue);
|
||
|
|
||
|
byte[] byteData = System.BitConverter.GetBytes(compressedHeight);
|
||
|
if ((toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightmapByteOrder == ToolboxHelper.ByteOrder.Mac) == System.BitConverter.IsLittleEndian)
|
||
|
{
|
||
|
data[index * 2 + 0] = byteData[1];
|
||
|
data[index * 2 + 1] = byteData[0];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
data[index * 2 + 0] = byteData[0];
|
||
|
data[index * 2 + 1] = byteData[1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float normalize = (1 << 8);
|
||
|
for (int y = 0; y < heightmapHeight; ++y)
|
||
|
{
|
||
|
for (int x = 0; x < heightmapWidth; ++x)
|
||
|
{
|
||
|
//Remapping the heightmap data
|
||
|
int index = x + y * heightmapWidth;
|
||
|
float remappedHeight = heights[y, x] * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
|
||
|
int height = Mathf.RoundToInt(remappedHeight * normalize);
|
||
|
byte compressedHeight = (byte)Mathf.Clamp(height, 0, byte.MaxValue);
|
||
|
data[index] = compressedHeight;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Compare both the original and regression test data
|
||
|
byte[] rawByteData = File.ReadAllBytes(path);
|
||
|
return data.SequenceEqual(rawByteData);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This overloaded method deals specifically with testing the level correction of the png and tga format
|
||
|
/// </summary>
|
||
|
/// <param name="toolboxWindow">Window where the Export Heightmap Utilities live</param>
|
||
|
/// <param name="minMaxRemap">Min and Max values of the remap</param>
|
||
|
/// <param name="path">String path of the files directory location</param>
|
||
|
/// <param name="format">Heightmap File Format</param>
|
||
|
/// <returns></returns>
|
||
|
bool TestLevelCorrection(TerrainToolboxWindow toolboxWindow, Vector2 minMaxRemap, string path, Heightmap.Format format)
|
||
|
{
|
||
|
//Execute the repro steps in code
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.ExportHeightRemapMin = minMaxRemap.x;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.ExportHeightRemapMax = minMaxRemap.y;
|
||
|
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightFormat = format;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ExportHeightmaps(new Object[] { m_TerrainComponent });
|
||
|
|
||
|
//Get heightmap data to compare
|
||
|
int width = RenderTexture.active.width - 1;
|
||
|
int height = RenderTexture.active.height - 1;
|
||
|
var texture = new Texture2D(width, height, RenderTexture.active.graphicsFormat, UnityEngine.Experimental.Rendering.TextureCreationFlags.None);
|
||
|
texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
||
|
|
||
|
//Remap Texture
|
||
|
Color[] pixels = texture.GetPixels();
|
||
|
for (int i = 0; i < pixels.Length; i += 4)
|
||
|
{
|
||
|
pixels[i].r = (pixels[i].r * 2) * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
pixels[i + 1].r = (pixels[i + 1].r * 2) * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
pixels[i + 2].r = (pixels[i + 2].r * 2) * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
pixels[i + 3].r = (pixels[i + 3].r * 2) * (minMaxRemap.y - minMaxRemap.x) + minMaxRemap.x;
|
||
|
}
|
||
|
texture.SetPixels(pixels);
|
||
|
texture.Apply();
|
||
|
|
||
|
//Compare both the original and regression test data
|
||
|
byte[] byteData = File.ReadAllBytes(path);
|
||
|
return format == Heightmap.Format.PNG ?
|
||
|
texture.EncodeToPNG().SequenceEqual(byteData) :
|
||
|
texture.EncodeToTGA().SequenceEqual(byteData);
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
public void TerrainToolboxUtilites_WhenSelectSplatmap_DoesNotIndexOutOfRange()
|
||
|
{
|
||
|
//Collect data, create needed objects
|
||
|
Texture2D texture = new Texture2D(512, 512);
|
||
|
TerrainLayer layer = new TerrainLayer();
|
||
|
layer.diffuseTexture = texture;
|
||
|
TerrainLayer[] terrainLayers = { layer };
|
||
|
|
||
|
//Create gameobject with terrain component
|
||
|
m_TerrainGO = new GameObject();
|
||
|
Terrain terrain = m_TerrainGO.AddComponent<Terrain>();
|
||
|
terrain.terrainData = new TerrainData();
|
||
|
|
||
|
//Add splatmap to terrain in order to import into the Utilities Window
|
||
|
terrain.terrainData.terrainLayers = terrainLayers;
|
||
|
|
||
|
//Execute the repro steps in code
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
Selection.activeGameObject = m_TerrainGO;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ImportSplatmapsFromTerrain();
|
||
|
|
||
|
Assert.That(() =>
|
||
|
{
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ExportSplatmapsToTerrain(true);
|
||
|
}, !Throws.TypeOf<System.IndexOutOfRangeException>());
|
||
|
toolboxWindow.Close();
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
public void TerrainToolboxUtilites_WhenApplySplatmaps_DoesNotDividebyZero()
|
||
|
{
|
||
|
// Preparation:
|
||
|
// Collect data, create needed objects
|
||
|
Texture2D texture = new Texture2D(512, 512);
|
||
|
TerrainLayer layer = new TerrainLayer();
|
||
|
layer.diffuseTexture = texture;
|
||
|
TerrainLayer[] terrainLayers = {layer};
|
||
|
|
||
|
//Add splatmap to terrain in order to import into the Utilities Window
|
||
|
m_TerrainComponent.terrainData.terrainLayers = terrainLayers;
|
||
|
Selection.activeGameObject = m_TerrainGO;
|
||
|
|
||
|
// Execute the repro steps in code
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ImportSplatmapsFromTerrain(true);
|
||
|
Selection.activeGameObject = null;
|
||
|
|
||
|
Assert.That(() =>
|
||
|
{
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.ExportSplatmapsToTerrain(true);
|
||
|
}, !Throws.TypeOf<System.DivideByZeroException>());
|
||
|
toolboxWindow.Close();
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
[TestCase(2, 33)]
|
||
|
[TestCase(2, 65)]
|
||
|
[TestCase(4, 65)]
|
||
|
[TestCase(2, 129)]
|
||
|
[TestCase(8, 129)]
|
||
|
[TestCase(2, 513)]
|
||
|
[TestCase(0, 65)]
|
||
|
[TestCase(-2, 65)]
|
||
|
[TestCase(-1, 65)]
|
||
|
public void TerrainToolboxUtilities_WhenSplitTerrain_HeightmapResolutionIsCorrect(int split, int originalHeightmapRes)
|
||
|
{
|
||
|
UnityEditor.Undo.IncrementCurrentGroup();
|
||
|
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
Texture2D gradientTexture = CreateGradientTexture();
|
||
|
int baseLevel = 0;
|
||
|
int remapLevel = 1;
|
||
|
int numberOfTiles = 1;
|
||
|
ToolboxHelper.CopyTextureToTerrainHeight(m_TerrainComponent.terrainData, gradientTexture, Vector2Int.zero, originalHeightmapRes, numberOfTiles, baseLevel, remapLevel);
|
||
|
|
||
|
Selection.activeGameObject = m_TerrainGO;
|
||
|
m_TerrainGO.name = "TestTerrain";
|
||
|
m_TerrainComponent.name = "TestComponent";
|
||
|
|
||
|
RenderTexture oldRT = RenderTexture.active;
|
||
|
RenderTexture.active = m_TerrainComponent.terrainData.heightmapTexture;
|
||
|
|
||
|
// Run the test
|
||
|
TestSplitTerrainHeightmapResolution(toolboxWindow, originalHeightmapRes, split);
|
||
|
|
||
|
AssetDatabase.Refresh();
|
||
|
RenderTexture.active = oldRT;
|
||
|
|
||
|
// Undo the split
|
||
|
UnityEditor.Undo.PerformUndo();
|
||
|
|
||
|
// Check that the original terrain heightmap resolution is unchanged
|
||
|
Assert.AreEqual(originalHeightmapRes, m_TerrainComponent.terrainData.heightmapResolution);
|
||
|
|
||
|
toolboxWindow.Close();
|
||
|
}
|
||
|
|
||
|
void TestSplitTerrainHeightmapResolution(TerrainToolboxWindow toolboxWindow, int heightmapRes, int split)
|
||
|
{
|
||
|
// Set up parent object so we can locate the split tiles for cleanup after testing
|
||
|
int groupingId = 12345;
|
||
|
var parent = new GameObject().AddComponent<TerrainGroup>();
|
||
|
parent.GroupID = groupingId;
|
||
|
m_TerrainComponent.transform.SetParent(parent.transform);
|
||
|
|
||
|
int actualSplit = split <= 1 ? 2 : split;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.HeightmapResolution = heightmapRes;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.TileSplit = actualSplit;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.SplitTerrain(m_TerrainComponent, groupingId, true);
|
||
|
|
||
|
// The children should include the original terrain gameobject + the newly created tiles
|
||
|
int childCount = parent.transform.childCount;
|
||
|
Assert.AreEqual(actualSplit * actualSplit + 1, childCount);
|
||
|
|
||
|
// Test and clean up the split tiles (skip the first child as it is the original terrain object)
|
||
|
for (int i = 1; i < childCount; i++)
|
||
|
{
|
||
|
var child = parent.transform.GetChild(i).GetComponent<Terrain>();
|
||
|
Assert.AreEqual(child.terrainData.heightmapResolution - 1,
|
||
|
GetExpectedTileHeightmapResolution(heightmapRes, actualSplit));
|
||
|
string path = Path.Combine("Assets/Terrain", child.transform.name + ".asset");
|
||
|
FileUtil.DeleteFileOrDirectory(path);
|
||
|
FileUtil.DeleteFileOrDirectory(path + ".meta");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int GetExpectedTileHeightmapResolution(int heightmapRes, int split)
|
||
|
{
|
||
|
int minHeightmapRes = 32;
|
||
|
int newHeightmapRes = (heightmapRes - 1) / split;
|
||
|
return Math.Max(newHeightmapRes, minHeightmapRes);
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
[TestCase(1, 1, 2)]
|
||
|
[TestCase(3, 3, 4)]
|
||
|
[TestCase(5, 5, 2)]
|
||
|
public void TerrainToolboxUtilites_WhenSplitTerrain_MissingTrees(int amountOfTreesX, int amountOfTreesZ, int tileSplit)
|
||
|
{
|
||
|
//Setup tree prefab (Needs to be persistent)
|
||
|
GameObject treePrefab = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||
|
treePrefab.GetComponent<Renderer>().sharedMaterial.shader = Shader.Find("Nature/Tree Soft Occlusion Bark");
|
||
|
string localPath = $"Assets/{treePrefab.name}.prefab";
|
||
|
localPath = AssetDatabase.GenerateUniqueAssetPath(localPath);
|
||
|
PrefabUtility.SaveAsPrefabAsset(treePrefab, localPath);
|
||
|
treePrefab = AssetDatabase.LoadAssetAtPath(localPath, typeof(GameObject)) as GameObject;
|
||
|
|
||
|
//Setup terrain object with trees
|
||
|
TerrainData terrainData = m_TerrainComponent.terrainData;
|
||
|
TreePrototype prototype = new TreePrototype();
|
||
|
prototype.prefab = treePrefab;
|
||
|
|
||
|
terrainData.treePrototypes = new TreePrototype[]
|
||
|
{
|
||
|
prototype
|
||
|
};
|
||
|
|
||
|
TreeInstance[] treeInstancesArray = new TreeInstance[amountOfTreesX*amountOfTreesZ];
|
||
|
for (int z = 0; z < amountOfTreesZ; z++)
|
||
|
{
|
||
|
for (int x = 0; x < amountOfTreesX; x++)
|
||
|
{
|
||
|
TreeInstance treeInstance = new TreeInstance();
|
||
|
treeInstance.prototypeIndex = 0;
|
||
|
treeInstance.position = new Vector3(x / (float)amountOfTreesX, 0, z / (float)amountOfTreesZ);
|
||
|
treeInstancesArray[(z * amountOfTreesZ) + x] = treeInstance;
|
||
|
}
|
||
|
}
|
||
|
terrainData.treeInstances = treeInstancesArray;
|
||
|
|
||
|
|
||
|
// Set up parent object so we can locate the split tiles for cleanup after testing
|
||
|
int groupingId = 12345;
|
||
|
var parent = new GameObject().AddComponent<TerrainGroup>();
|
||
|
parent.GroupID = groupingId;
|
||
|
m_TerrainComponent.transform.SetParent(parent.transform);
|
||
|
|
||
|
//Execute the repro steps checking to make sure split terrains have trees
|
||
|
Selection.activeGameObject = m_TerrainGO;
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.m_Settings.TileSplit = tileSplit;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.SplitTerrains(true);
|
||
|
|
||
|
Terrain[] objs = GameObject.FindObjectsOfType<Terrain>();
|
||
|
Terrain[] splitTerrains = objs.Where(
|
||
|
obj => obj.terrainData?.treeInstanceCount > 0
|
||
|
).ToArray();
|
||
|
Assert.IsNotEmpty(splitTerrains);
|
||
|
|
||
|
//Cleanup
|
||
|
toolboxWindow.Close();
|
||
|
FileUtil.DeleteFileOrDirectory("Assets/Terrain");
|
||
|
File.Delete("Assets/Terrain.meta");
|
||
|
File.Delete(localPath);
|
||
|
File.Delete(localPath + ".meta");
|
||
|
UnityEditor.AssetDatabase.Refresh();
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
public void TerrainToolboxUtilities_WhenApplySplatmaps_DoesNotModifyColorData()
|
||
|
{
|
||
|
//Setup terrain layer data
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow<TerrainToolboxWindow>();
|
||
|
TerrainLayer layer = new TerrainLayer();
|
||
|
layer.diffuseTexture = CreateGradientTexture();
|
||
|
byte[] texData = layer.diffuseTexture.GetRawTextureData();
|
||
|
m_TerrainComponent.terrainData.terrainLayers = new TerrainLayer[] { layer };
|
||
|
|
||
|
//Reproduce steps
|
||
|
Selection.activeObject = m_TerrainGO;
|
||
|
TerrainToolboxUtilities utilities = toolboxWindow.m_TerrainUtilitiesMode;
|
||
|
utilities.ImportSplatmapsFromTerrain();
|
||
|
|
||
|
//Manually set splatmap list since the window's OnGUI method isn't called which normally sets the splatmap list
|
||
|
utilities.m_SplatmapList = new UnityEditorInternal.ReorderableList(utilities.m_Splatmaps, typeof(Texture2D), true, false, true, true);
|
||
|
utilities.ExportSplatmapsToTerrain(true);
|
||
|
|
||
|
Assert.AreEqual(texData, layer.diffuseTexture.GetRawTextureData());
|
||
|
}
|
||
|
|
||
|
int CountTerrainComponents(GameObject go)
|
||
|
{
|
||
|
return go.GetComponents<Component>().Select(x => x.GetType())
|
||
|
.Count(x => x == typeof(Terrain) || x == typeof(TerrainCollider));
|
||
|
}
|
||
|
|
||
|
[Test]
|
||
|
public void TerrainToolboxUtilities_WhenTerrainsRemoved_AreChildrenRemoved()
|
||
|
{
|
||
|
// Set up some terrain data
|
||
|
var terrainDatas = new TerrainData[3];
|
||
|
for (int i = 0; i < 3; ++i)
|
||
|
{
|
||
|
terrainDatas[i] = new TerrainData();
|
||
|
terrainDatas[i].heightmapResolution = 256;
|
||
|
AssetDatabase.CreateAsset(terrainDatas[i], $"Assets/Test Terrain Data{i}.asset");
|
||
|
}
|
||
|
|
||
|
// Set up terrains
|
||
|
Texture2D gradientTexture = CreateGradientTexture();
|
||
|
var terrainAlone = Terrain.CreateTerrainGameObject(terrainDatas[0]);
|
||
|
|
||
|
var terrainWithOtherComponents = Terrain.CreateTerrainGameObject(terrainDatas[1]);
|
||
|
terrainWithOtherComponents.AddComponent<AudioSource>();
|
||
|
|
||
|
var terrainWithChild = Terrain.CreateTerrainGameObject(terrainDatas[2]);
|
||
|
var child = new GameObject();
|
||
|
child.transform.SetParent(terrainWithChild.transform);
|
||
|
|
||
|
AssetDatabase.Refresh();
|
||
|
|
||
|
// Set up the selection with all the terrain objects
|
||
|
Selection.objects = new GameObject[] {
|
||
|
terrainAlone.gameObject,
|
||
|
terrainWithOtherComponents.gameObject,
|
||
|
terrainWithChild.gameObject
|
||
|
};
|
||
|
|
||
|
//Execute the repro steps checking to make sure split terrains have trees
|
||
|
TerrainToolboxWindow toolboxWindow = EditorWindow.GetWindow(typeof(TerrainToolboxWindow)) as TerrainToolboxWindow;
|
||
|
toolboxWindow.m_TerrainUtilitiesMode.RemoveTerrains(true);
|
||
|
|
||
|
AssetDatabase.Refresh();
|
||
|
|
||
|
// Check the correct gameobjects have been destroyed
|
||
|
Assert.IsTrue(terrainAlone == null);
|
||
|
Assert.IsFalse(terrainWithOtherComponents == null);
|
||
|
Assert.IsFalse(terrainWithChild == null);
|
||
|
Assert.IsFalse(child == null);
|
||
|
|
||
|
// Check number of terrain components
|
||
|
Assert.AreEqual(CountTerrainComponents(terrainWithOtherComponents), 0);
|
||
|
Assert.AreEqual(terrainWithOtherComponents.GetComponents<Component>().Length - CountTerrainComponents(terrainWithOtherComponents), 2);
|
||
|
Assert.AreEqual(CountTerrainComponents(terrainWithChild), 0);
|
||
|
|
||
|
// Check children are not destroyed
|
||
|
Assert.AreEqual(terrainWithChild.transform.childCount, 1);
|
||
|
|
||
|
// clean up
|
||
|
GameObject.DestroyImmediate(terrainWithOtherComponents);
|
||
|
GameObject.DestroyImmediate(terrainWithChild);
|
||
|
toolboxWindow.Close();
|
||
|
}
|
||
|
|
||
|
[SetUp]
|
||
|
public void Setup()
|
||
|
{
|
||
|
m_NumTerrains = Terrain.activeTerrains.Length;
|
||
|
var terrainData = new TerrainData();
|
||
|
m_TerrainGO = Terrain.CreateTerrainGameObject(terrainData);
|
||
|
m_TerrainComponent = m_TerrainGO.GetComponent<Terrain>();
|
||
|
}
|
||
|
|
||
|
[TearDown]
|
||
|
public void Cleanup()
|
||
|
{
|
||
|
TerrainGroup group = null;
|
||
|
var parent = m_TerrainGO.transform.parent;
|
||
|
if (parent != null)
|
||
|
{
|
||
|
group = parent.GetComponent<TerrainGroup>();
|
||
|
}
|
||
|
|
||
|
if (group != null)
|
||
|
{
|
||
|
var terrains = group.GetComponentsInChildren<Terrain>();
|
||
|
foreach (var t in terrains)
|
||
|
{
|
||
|
var go = t.gameObject;
|
||
|
Object.DestroyImmediate(t.terrainData);
|
||
|
Object.DestroyImmediate(t);
|
||
|
Object.DestroyImmediate(go);
|
||
|
}
|
||
|
|
||
|
Object.DestroyImmediate(group.gameObject);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Object.DestroyImmediate(m_TerrainComponent.terrainData);
|
||
|
Object.DestroyImmediate(m_TerrainComponent);
|
||
|
Object.DestroyImmediate(m_TerrainGO);
|
||
|
}
|
||
|
|
||
|
m_TerrainComponent = null;
|
||
|
Selection.activeObject = null;
|
||
|
|
||
|
Assert.True(m_NumTerrains == Terrain.activeTerrains.Length, $"Leaked {Terrain.activeTerrains.Length - m_NumTerrains} Terrain objects. Please make sure the test is cleaning up created Terrains.");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a gradient texture
|
||
|
/// </summary>
|
||
|
/// <param name="width">Width of the texture</param>
|
||
|
/// <param name="height">Height of the texture</param>
|
||
|
/// <returns></returns>
|
||
|
Texture2D CreateGradientTexture(int width = 513, int height = 513)
|
||
|
{
|
||
|
Gradient gradient = new Gradient();
|
||
|
GradientColorKey[] colorKeys =
|
||
|
{
|
||
|
new GradientColorKey{color = Color.white, time = 0f},
|
||
|
new GradientColorKey{color = Color.black, time = 1f},
|
||
|
};
|
||
|
GradientAlphaKey[] alphaKeys =
|
||
|
{
|
||
|
new GradientAlphaKey{alpha = 1f, time= 0f},
|
||
|
new GradientAlphaKey{alpha = 0f, time= 1f},
|
||
|
};
|
||
|
|
||
|
gradient.SetKeys(colorKeys, alphaKeys);
|
||
|
var gradTex = new Texture2D(width, height, TextureFormat.R16, false);
|
||
|
gradTex.filterMode = FilterMode.Bilinear;
|
||
|
|
||
|
float inv = 1f / (width - 1);
|
||
|
for (int y = 0; y < height; y++)
|
||
|
{
|
||
|
for (int x = 0; x < width; x++)
|
||
|
{
|
||
|
var t = x * inv;
|
||
|
Color col = gradient.Evaluate(t);
|
||
|
gradTex.SetPixel(x, y, col);
|
||
|
}
|
||
|
}
|
||
|
gradTex.Apply();
|
||
|
return gradTex;
|
||
|
}
|
||
|
}
|
||
|
}
|