To properly map every pixel of a heightmap image to a terrain, we will end up with a terrain both in width and length 1 unit less that the actual width and length of the heightmap. For instance, if we have an image with just 2 pixels wide, each pixel will map to each vertex of a quad. We have 2 pixels but only 1 unit of terrain.
I'm trying to "fix" this problem by scaling the terrain to fill the empty quad in the end of the terrain. However, this is being problematic when I try to get the height of the terrain in some (x,z) point. The problem is not exactly the scaling as you'll see.
First, let's look at the code that works. The one where the scaling does not take into account the missing quad. I have a 1024x1024 heightmap which will create a terrain with 1023x1023 quads (I'm actually using triangles, but it's easy to explain with quads) without any scaling.
Now let's scale the terrain to something like 2000x2000 by passing the appropriate arguments to the following function:
void Terrain::ScaleDimension(float dimWidth, float dimLength) {
if(dimWidth <= 0.0f || dimLength <= 0.0f) return;
stepWidth = dimWidth / width; // 1.953125 = 2000 / 1024
stepLength = dimLength / length; // 1.953125 = 2000 / 1024
}
When I draw the terrain, I use stepWidth
and stepLength
to properly scale the terrain accordingly. The terrain will be scaled, but only 1023 quads will be used, always 1023, no more (which is fine). Now let's say I'm moving my game character and I need to get the terrain height in the current player position. I have a function that will take the x
and z
coordinates of the player position and return the height at that point. But since the terrain dimension is scaled, I can't just use the x
and z
passed to the function, I need to calculate the right ones, like this:
x = x / stepWidth;
z = z / stepLeng开发者_JAVA百科th;
After that, I just need to find the 4 adjacent vertices of the new x
and z
and perform a bilinear interpolation on all vertices heights to calculate the correct height on the player's position.
This is all working just fine. The terrain is properly drawn and all heights are properly calculated. My problem is when I try to get around the "limitation" explained in the beginning of this question.
To get around the problem I change my scale function to this:
void Terrain::ScaleDimension(float dimWidth, float dimLength) {
if(dimWidth <= 0.0f || dimLength <= 0.0f) return;
stepWidth = dimWidth / (width - 1.0f);
stepLength = dimLength / (length - 1.0f);
}
Let's say my terrain size (the values I pass to the scale function above) is exactly the same size of the heightmap, that is 1024x1204. This will give me a step of 1.000977517106549364613880742913
which will make the 1023 quads to exactly fit from 0 to 1024 (the size of the terrain). So far so good, the terrain is drawn exactly as I expected.
The real problem is calculating the height. I've tried many things but I just can't figure it out the equation to calculate the correct x
and z
to be used on the function that returns the height, given the new step calculation above. Diving x
and z
with the step size like I did before simply won't cut it. The step is now calculated a little differently and not as simple as before.
If you want to go from 0..1023
scaled up to 0..1999
you need to sample along each position to get the proper values.
Given: old new
0,0 == 0,0
0,1023 == 0,1999
1023,0 == 1999,0
1023,1023 == 1999,1999
In other words, the four corners stay the same.
Now, you need to compute the height of any given point, you'd call it like this:
float height = convert(1500,1450,1024,2000);
float convert(int newx, int newy, int origScale, int newScale) {
float factor = ((float)(origScale)) / (float)(newScale);
float oldx = newx * factor;
float oldy = newy * factor;
int oldxlo = floorInt(oldx); // oldx == 3.4, oldxlo = 3
int oldxhi = ceilInt(oldx); // oldx == 3.4, oldxhi = 34
int oldylo = floorInt(oldy); // oldy == 3.4, oldylo = 3
int oldyhi = ceilInt(oldy); // oldy == 3.4, oldyhi = 34
float topLeftHeight = getHeight(oldxlo,oldylo);
float topRightHeight = getHeight(oldxhi,oldylo);
float botLeftHeight = getHeight(oldxlo,oldyhi);
float botRightHeight = getHeight(oldxhi,oldyhi);
// now you have the heights of the four corners of the old grid
// that surround the new grid. You can use a form of interpolation on this grid,
// I'm sure you can find one somewhere.
Point3D topLeft(oldxlo,oldylo,topLeftHeight);
Point3D topRight(oldxhi,oldylo,topRightHeight);
Point3D botLeft(oldxlo,oldyhi,botLeftHeight);
Point3D botRight(oldxhi,oldyhi,botRightHeight);
// or something
return bilinearInterpolate(topLeft,topRight,botLeft,botRight,newx,newy);
}
Methods are too simplistic. What about complex 3D terrain with buildings and objects? I've used glReadPixels to read the depth buffer & use some simple math to find the height-above-terrain anywhere in a scene.
精彩评论