- Home /
TerrainData with non-power of 2 samples
I am downloading real elevation samples, but the samples I get aren't necessarily power of 2, not even square sometimes, which means the area (bounding box) covered by the samples is smaller than heightMapResolution*sample distance. I want to calculate the elevation at a real world position with X,Y coordinates. The BoundingBox is in real world coordinates, basically 4 numbers (left,bottom,right,top). I don't manage to normalise X and Y correctly to pass to TerrainData.GetInterpolatedHeight(x,y)
Here is my class:
public class AVTerrain
{
public URN Urn
{
get;
private set;
}
/// <summary>
/// The area covered by the grid
/// </summary>
public BoundingBox Bbox
{
get;
private set;
}
/// <summary>
/// The TerrainData
/// </summary>
public TerrainData TerrainData
{
get;
private set;
}
/// <summary>
/// Real world altitude corresponding to the zero-altitude of this dataset.
/// </summary>
/// <value>The minimum altitude.</value>
public float MinAltitude
{
get;
private set;
}
private AVTerrain()
{ }
public AVTerrain(URN urn, BoundingBox bbox, float mapHeight, float minAltitude, float[,] samples)
{
Urn = urn;
if (samples != null && samples.Length != 0)
{
Bbox = bbox.Copy();
MinAltitude = minAltitude;
TerrainData = new TerrainData();
int rows = samples.GetLength(0);
int cols = samples.GetLength(1);
int r = Mathf.Max(cols, rows);
//When setting the heightMapResolution, Unity may overrwrite the passed value to the nearest power of 2
//=> may not be exactly what we first set it and needs be changed if < r+1
TerrainData.heightmapResolution = r;
int powerOf2 = TerrainData.heightmapResolution - 1;
if (powerOf2 < r)
{
TerrainData.heightmapResolution = (TerrainData.heightmapResolution - 1) * 2;
powerOf2 = TerrainData.heightmapResolution - 1;
}
//The terain size has a minimum value of32*32
//This calculation does not seem to be exactly right as tests fail
//but can't figure out how to calculate the size correctly
float xSampleDistance = (float)bbox.Width / (cols-1) ;
float ySampleDistance = (float)bbox.Height / (rows-1);
TerrainData.size = new Vector3((powerOf2 - 1) * xSampleDistance, mapHeight, (powerOf2 - 1) * ySampleDistance);
TerrainData.SetHeights(0, 0, samples);
//Extend by one pixel if sample is non-power of 2 to avoid interpolating with zero on top and right
if(rows < TerrainData.heightmapResolution-1)
{
TerrainData.SetHeights(0, rows, samples.GetRow(rows-1));
}
if (cols < TerrainData.heightmapResolution-1)
{
TerrainData.SetHeights(cols, 0, samples.GetColumn(cols - 1));
}
if(rows < TerrainData.heightmapResolution-1 && cols < TerrainData.heightmapResolution-1)
{
TerrainData.SetHeights(cols, rows, new float[1, 1] { { samples[rows - 1, cols - 1] } });
}
}
}
/// <summary>
/// Get the elevation at the X and Y coordinates in real world coordinates.
/// </summary>
/// <param name="x">X coordinate</param>
/// <param name="y">Y coordinate</param>
/// <returns>The elevation</returns>
public float GetElevation(double x, double y)
{
if (!Bbox.ContainsGeoPoint(new Vector2d(x, y)))
{
throw new ArgumentOutOfRangeException("The coordinates are out of the terrain bounds.");
}
//Normalize and adjust coordinates
float x_ = (float)(x - Bbox.X1) / TerrainData.size.x;
float y_ = (float)(y - Bbox.Y1) / TerrainData.size.z;
return TerrainData.GetInterpolatedHeight(x_, y_) + MinAltitude;
}
}
Here is my test (failing):
public class AVTerrainTest
{
float[,] samples;
const int cols = 240, rows = 160;//unity min terrain size is 32*32
const double bboxWidth = 239;
const double bboxHeight = 159;
const float sampleDistance = 1;
BoundingBox bbox = new BoundingBox(0, 0, bboxWidth, bboxHeight);
AVTerrain terrain;
[OneTimeSetUp]
public void OneTimeSetup ()
{
samples = new float[rows, cols];
}
[Test]
public void EnsuresTerrainValuesAreCorrect_AtTopRIght()
{
//Arrange
samples[rows-1, cols-1] = .5f;
terrain = new AVTerrain(new URN("terrain"), bbox, 1, 0, samples);
//Act
float elevationAtTopRight = terrain.GetElevation(bboxWidth, bboxHeight);
float elevationAtTopRightMinus1SampleDistance = terrain.GetElevation(bboxWidth - sampleDistance, bboxHeight - sampleDistance);
//Assert
Assert.AreEqual(.5f, elevationAtTopRight, 0.005);
Assert.AreEqual(0.0f, elevationAtTopRightMinus1SampleDistance, 0.005);
}
}
Can you link to the elevation data and I can try to write an example from scratch for you
@sacredgeometry thanks for the offer, but my problem here is not the data, it's the calculation of the TerrainData.size in the constructor or the calculation the x_ and y_ coordinates in GetElevation(x,y). The test provides a sample array, I want to pass the test with the provided samples.
Answer by g-augview · Aug 22, 2019 at 11:36 PM
finally got it by replacing line 92 TerrainData.size = new Vector3((powerOf2 - 1) * xSampleDistance, mapHeight, (powerOf2 - 1) * ySampleDistance);
with TerrainData.size = new Vector3(powerOf2 * xSampleDistance, mapHeight, powerOf2 * ySampleDistance);