Terrain : bug in TerrainData.SetHeights

Actual code of TerrainData.SetHeights (with ILSpy) :

public void SetHeights(int xBase, int yBase, float[,] heights)
{
	if (heights == null)
	{
		throw new NullReferenceException();
	}
	if (xBase + heights.GetLength(1) > this.heightmapWidth || xBase < 0 || yBase < 0 || yBase + heights.GetLength(0) > this.heightmapHeight)
	{
		throw new Exception(UnityString.Format("X or Y base out of bounds. Setting up to {0}x{1} while map size is {2}x{3}", new object[]
		{
			xBase + heights.GetLength(1),
			yBase + heights.GetLength(0),
			this.heightmapWidth,
			this.heightmapHeight
		}));
	}
	this.Internal_SetHeights(xBase, yBase, heights.GetLength(1), heights.GetLength(0), heights);
}

But when we try this on a 513 * 513 heightmap :

terrain.terrainData.SetHeights (512, 511, new[,] { { 0.5f, 0.5f } });

We get the error : “Exception: X or Y base out of bounds. Setting up to 514x512 while map size is 513x513” while the exception should be thrown when we do :

terrain.terrainData.SetHeights (511, 512, new[,] { { 0.5f, 0.5f } });

So in SetHeights, GetLength (0) must be swapped with GetLength (1) :

public void SetHeights(int xBase, int yBase, float[,] heights)
{
	if (heights == null)
	{
		throw new NullReferenceException();
	}
	if (xBase + heights.GetLength(0) > this.heightmapWidth || xBase < 0 || yBase < 0 || yBase + heights.GetLength(1) > this.heightmapHeight)
	{
		throw new Exception(UnityString.Format("X or Y base out of bounds. Setting up to {0}x{1} while map size is {2}x{3}", new object[]
		{
			xBase + heights.GetLength(0),
			yBase + heights.GetLength(1),
			this.heightmapWidth,
			this.heightmapHeight
		}));
	}
	this.Internal_SetHeights(xBase, yBase, heights.GetLength(0), heights.GetLength(1), heights);
}

While waiting for the fix, I’ve written this code to get around the bug :

public static class TerrainHack
{
	private static MethodInfo internal_setHeights;

	static TerrainHack ()
	{
		Init ();
	}

	private static void Init ()
	{
		if (internal_setHeights == null)
			internal_setHeights = typeof (TerrainData).GetMethod ("Internal_SetHeights", BindingFlags.Instance | BindingFlags.NonPublic);
	}

	public static void SetHeightsCorrect (this TerrainData terrainData, int xBase, int yBase, float[,] heights)
	{
		SetHeightsCorrect (terrainData, xBase, yBase, heights.GetLength (0), heights.GetLength (1), heights);
	}

	public static void SetHeightsCorrect (this TerrainData terrainData, int xBase, int yBase, int width, int height, float[,] heights)
	{
		if (terrainData == null)
			throw new ArgumentNullException ("terrainData");
		if (heights == null)
			throw new ArgumentNullException ("heights");

		if (xBase < 0 ||
			yBase < 0 ||
			xBase + width > terrainData.heightmapWidth ||
			yBase + height > terrainData.heightmapHeight
			)
		{
			throw new Exception (string.Format (
				"X or Y base out of bounds. Setting up to {0}x{1} while map size is {2}x{3}", 
				xBase + width,
				yBase + height,
				terrainData.heightmapWidth,
				terrainData.heightmapHeight
			));
		}

		_SetHeights (terrainData, xBase, yBase, width, height, heights);
	}

	private static void _SetHeights (this TerrainData terrainData, int xBase, int yBase, int width, int height, float[,] heights)
	{
		Init ();
		internal_setHeights.Invoke (terrainData, new object[] { xBase, yBase, width, height, heights });
	}
}

(sorry for posting here but i can’t use the bug reporter)

1 Like

Any particular reason why not? Bugs typically won’t get fixed by posting here; they have to go through the bug reporter.

–Eric