- Joined
- Jun 18, 2004
- Messages
- 119
Introduction:
Depending on the movement type of a unit, the pathing can be different. For instance, flying units can fly over ground blockers and "foot" units can walk over fly blockers. This behavior can be exploited in different situations. However, changing a unit's movement type also leads to differences in other behavior.
For example, flying units have a "natural flyheight", that is, without the user setting it's flyheight by object editor or triggers, the unit will automatically change its flyheight depending on the surrounding terrain. This basically means that units with the movement type "flying" cannot be used if one wants absolute control of the unit's flyheight, because there is a lack of the function "GetUnitAbsoluteFlyheight", or something similar (the function GetUnitFlyheight will not return the added flyheight by surrounding terrain). To be clear: I am not talking about the fact that you should subtract GetLocationZ from the flyheight to get the absolute height, I am well aware of this. However, as will be illustrated below, the height of the surrounding (that is, not only the one directly below the unit) influences its height.
Therefore, we must "reverse engineer" the method blizzard uses to add flyheight to flying units.
Method:
The method consists of creating a grid of flying units (with a flyheight of 0, I used makura missile as model) over terrain which has been altered. Then, a unit with "foot" movement type is placed at the same place, and 'Amrf' is added and remove to allow manipulation of its flyheight. It's flyheight is then altered by triggering (for example, a unit input command can set its flyheight) and it is visually checked to be of the same height. The process is repeated for different locations to find a formula that fits. For the purpose of clarity, I will refer to the added natural flyheight of the unit with flying movement type as the "z".
Results:
From the beginning, it was clear that the position of the "mountain" (that is, a place with raised terrain) is important. Terrain can be raised at a grid of 128, but if the gridpoint is "even" (that is, 0, 256, 512, etc) it seems to increase the z of the "odd" (that is, 128,384, etc) gridpoints around it, see Figure 1. I thus assumed that even though terrain can be raised at 128 gridpoints, the process that adds z is calculated based on a 256 grid.
Because of this reason, I began to only raise the odd gridpoints in order to make life easier. The odd gridpoint seemed to raise the z of surround terrain on the x-axis up to a distance of 512 in a straight line. Otherwise said (as we assumed a 256 grid), it raises the z of the gridpoints that are a distance of 256 away to 1/2 the height of the mountain. It behaves exactly the same in the y direction. Pretty straightforward so far.
Now, let's build a grid around this mountain, and try to calculate the flyheight gridpoints. As expected, the mountain influences the z around it in both the x and y direction, but also when x and y are both not 0, that is, diagonally the z is also raised (see Figure 2).
the formula for 1 mountain is thus pretty straightforward. gridpoints at 256 away in 1 direction are raised by 1/2 the height of the mountain, gridpoints at 256 away in both directions (diagonally) are raised by 1/4 the height of the mountain. This calculation was perform, and the z of gridpoints indeed got raised correctly (see the bloodmage missile units in Figure 2). I then added a second mountain a distance of 512 away of the same height to see what would happen. As I hoped, the calculations worked flawlessly. I expected a "bridge" to form between the two mountains as (1/2)*h+(1/2)*h = h. This was indeed the situation as depicted in Figure 3, left.
Problems started to rise when the mountains started to be in the range of the other mountain, as depicted in Figure 3, right. As according to our formula the z of a terrainpoint is raised by 1/2*h by a mountain 256 away in one direction, and by 1/4*h by a mountain 256 away in both directions, we expected the points marked in red to be higher. However, this seems to not be the case. My code at that moment was the following:
In view of future problems with even gridpoints, I am calculating this based on a 128 spaced grid, thus, flyheight gridpoints are spaced 2 apart. The function .getZ returns the GetLocationZ of the real position of the gridpoint, and the function .addZ adds the z to the gridpoint's unit at the real position of the gridpoint.
Obviously, two connected mountains will lead to problems, but how do we sort this? There has to be an order of importance to the increasing of z. To try and find the logic here we test various scenarios. The first two are shown in Figure 3, the third scenario is depicted in Figure 4.
The algorithm appearantly differentiates both axis in some way. From the observations we can deduct that: if the two mountains are on the same axis, they both contribute half their height to z to form a bridge. If the axis is different, however, it is less. I therefore used the following code:
By doing so, the smaller mountain contributes less to the height if the axis are different, because it takes the z added of the previously added mountain in consideration. This seems to be correct, as illustrated in Figure 5.
The algorithm now correctly deals with the error in Figure 4, but the error in Figure 3,right is still present. This is because the corner mountains influence the added z depending on nearby "side" mountains. Once this is taken into consideration, the algorithm works at odd gridpoints perfectly, as depicted in Figure 6. The code for this is shown below:
The only thing left afterwards is to integrate the even gridpoints. They behave in a funny way: the height of the center mountain (that is, h[4]) is increased by all surrounding even gridpoints, and the height of the side-mountains (that is, h[1,3,5,7]) is increased by the surrounding gridpoints perpendicular to the vector to the center. I don't know how I can explain this better. Anyway, the current code illustrated below works to define a FlyHeightGrid, as can be seen in Figure 7.
Current code:
Conclusion:
The flyheight that is automatically added on unit's with movement type "Fly" by the surrounding (that is, not only the terrain directly underneath) can be calculated with the above algorithm. Although not very elegant, it is the only way to do this. I will in the near future release a resource that can get a flying unit's added flyheight based on this algorithm.
Thanks,
iNfraNe
Depending on the movement type of a unit, the pathing can be different. For instance, flying units can fly over ground blockers and "foot" units can walk over fly blockers. This behavior can be exploited in different situations. However, changing a unit's movement type also leads to differences in other behavior.
For example, flying units have a "natural flyheight", that is, without the user setting it's flyheight by object editor or triggers, the unit will automatically change its flyheight depending on the surrounding terrain. This basically means that units with the movement type "flying" cannot be used if one wants absolute control of the unit's flyheight, because there is a lack of the function "GetUnitAbsoluteFlyheight", or something similar (the function GetUnitFlyheight will not return the added flyheight by surrounding terrain). To be clear: I am not talking about the fact that you should subtract GetLocationZ from the flyheight to get the absolute height, I am well aware of this. However, as will be illustrated below, the height of the surrounding (that is, not only the one directly below the unit) influences its height.
Therefore, we must "reverse engineer" the method blizzard uses to add flyheight to flying units.
Method:
The method consists of creating a grid of flying units (with a flyheight of 0, I used makura missile as model) over terrain which has been altered. Then, a unit with "foot" movement type is placed at the same place, and 'Amrf' is added and remove to allow manipulation of its flyheight. It's flyheight is then altered by triggering (for example, a unit input command can set its flyheight) and it is visually checked to be of the same height. The process is repeated for different locations to find a formula that fits. For the purpose of clarity, I will refer to the added natural flyheight of the unit with flying movement type as the "z".
Results:
From the beginning, it was clear that the position of the "mountain" (that is, a place with raised terrain) is important. Terrain can be raised at a grid of 128, but if the gridpoint is "even" (that is, 0, 256, 512, etc) it seems to increase the z of the "odd" (that is, 128,384, etc) gridpoints around it, see Figure 1. I thus assumed that even though terrain can be raised at 128 gridpoints, the process that adds z is calculated based on a 256 grid.
Because of this reason, I began to only raise the odd gridpoints in order to make life easier. The odd gridpoint seemed to raise the z of surround terrain on the x-axis up to a distance of 512 in a straight line. Otherwise said (as we assumed a 256 grid), it raises the z of the gridpoints that are a distance of 256 away to 1/2 the height of the mountain. It behaves exactly the same in the y direction. Pretty straightforward so far.
Now, let's build a grid around this mountain, and try to calculate the flyheight gridpoints. As expected, the mountain influences the z around it in both the x and y direction, but also when x and y are both not 0, that is, diagonally the z is also raised (see Figure 2).
the formula for 1 mountain is thus pretty straightforward. gridpoints at 256 away in 1 direction are raised by 1/2 the height of the mountain, gridpoints at 256 away in both directions (diagonally) are raised by 1/4 the height of the mountain. This calculation was perform, and the z of gridpoints indeed got raised correctly (see the bloodmage missile units in Figure 2). I then added a second mountain a distance of 512 away of the same height to see what would happen. As I hoped, the calculations worked flawlessly. I expected a "bridge" to form between the two mountains as (1/2)*h+(1/2)*h = h. This was indeed the situation as depicted in Figure 3, left.
Problems started to rise when the mountains started to be in the range of the other mountain, as depicted in Figure 3, right. As according to our formula the z of a terrainpoint is raised by 1/2*h by a mountain 256 away in one direction, and by 1/4*h by a mountain 256 away in both directions, we expected the points marked in red to be higher. However, this seems to not be the case. My code at that moment was the following:
JASS:
set i = 0
set r = 0.
//perform a loop to get the surrounding heights of the terrain, and fill array h[] from 0 to 8 (0 = left bottom, 8 = right top, filled horizontally)
loop
set rel_y = (i/3)-1
set rel_x = (i-(i/3)*3)-1
set h[i] = .getZ(x+rel_x*2,y+rel_y*2)
set i = i + 1
exitwhen i > 8
endloop
// set the z to the origin (point 4)
set z = h[4]
// loop through the gridPoints 256 away in 1 direction and add their height difference between them and the origin / 2
set i = 1
loop
if h[i] > h[4] then
set z = z + (h[i]-h[4])/2
endif
set i = i + 2
exitwhen i > 7
endloop
// loop through the gridPoints 256 away in both directions and add their height difference between them and the origin / 2
set i = 0
loop
if h[i] > h[4] then
set z = z + (h[i]-h[4])/4
endif
set i = i + 2
exitwhen i > 8
endloop
// display the calculated z
call .addZ(x,y,z)
Obviously, two connected mountains will lead to problems, but how do we sort this? There has to be an order of importance to the increasing of z. To try and find the logic here we test various scenarios. The first two are shown in Figure 3, the third scenario is depicted in Figure 4.
The algorithm appearantly differentiates both axis in some way. From the observations we can deduct that: if the two mountains are on the same axis, they both contribute half their height to z to form a bridge. If the axis is different, however, it is less. I therefore used the following code:
JASS:
if RMaxBJ(h[1],h[7]) > RMaxBJ(h[3],h[5]) then // blizz uses the highest mountain as preference.
set z = z + (RMaxBJ(h[3]-z,0.)+RMaxBJ(h[5]-z,0.))/2
set z = z + (RMaxBJ(h[1]-z,0.)+RMaxBJ(h[7]-z,0.))/2
else
set z = z + (RMaxBJ(h[1]-z,0.)+RMaxBJ(h[7]-z,0.))/2
set z = z + (RMaxBJ(h[3]-z,0.)+RMaxBJ(h[5]-z,0.))/2
endif
// replacing:
//set i = 1
//loop
// if h[i] > h[4] then
// set z = z + (h[i]-h[4])/2
// endif
// set i = i + 2
// exitwhen i > 7
//endloop
By doing so, the smaller mountain contributes less to the height if the axis are different, because it takes the z added of the previously added mountain in consideration. This seems to be correct, as illustrated in Figure 5.
The algorithm now correctly deals with the error in Figure 4, but the error in Figure 3,right is still present. This is because the corner mountains influence the added z depending on nearby "side" mountains. Once this is taken into consideration, the algorithm works at odd gridpoints perfectly, as depicted in Figure 6. The code for this is shown below:
JASS:
// do the corners
set i = 0
loop
set r = 0.
// depending on the current position, get the highest "side" mountain
if i == 0 then
set r = RMaxBJ(h[1],h[3])
elseif i == 2 then
set r = RMaxBJ(h[1],h[5])
elseif i == 6 then
set r = RMaxBJ(h[3],h[7])
elseif i == 8 then
set r = RMaxBJ(h[7],h[5])
endif
if h[i] > r and h[4] < h[i] and i != 4 then
set z = z + ((h[i]-RMaxBJ(h[4],r))/4) // calculate based on the highest point, either that at the origin, or of the side-mountain
endif
set i = i + 2
exitwhen i > 8
endloop
//replacing
//set i = 0
//loop
// if h[i] > h[4] then
// set z = z + (h[i]-h[4])/4
// endif
// set i = i + 2
// exitwhen i > 8
//endloop
The only thing left afterwards is to integrate the even gridpoints. They behave in a funny way: the height of the center mountain (that is, h[4]) is increased by all surrounding even gridpoints, and the height of the side-mountains (that is, h[1,3,5,7]) is increased by the surrounding gridpoints perpendicular to the vector to the center. I don't know how I can explain this better. Anyway, the current code illustrated below works to define a FlyHeightGrid, as can be seen in Figure 7.
Current code:
JASS:
set z = 0.
set i = 0
set r = 0.
//do the even points stuff. (search in 128 area the highest point and use that as height)
loop
set rel_y = (i/3)-1
set rel_x = (i-(i/3)*3)-1
if i != 4 then // if it is not the center, simply set it to the height of the point
set h[i] = .getZ(x+rel_x*2,y+rel_y*2)
// the sidenodes are increased by surround even gridpoints!
if rel_x == 0 then
set r = RMaxBJ(.getZ(x-1,y+rel_y*2),.getZ(x+1,y+rel_y*2))
if h[i] < r then
set h[i] = r
endif
endif
if rel_y == 0 then
set r = RMaxBJ(.getZ(x+rel_x*2,y-1),.getZ(x+rel_x*2,y+1))
if h[i] < r then
set h[i] = r
endif
endif
endif
if .getZ(x+rel_x,y+rel_y) > h[4] then // for the center one [4] check the tiles surrounding it at 128 distances
set h[4] = .getZ(x+rel_x,y+rel_y)
endif
set i = i + 1
exitwhen i > 8
endloop
set z = h[4]
// do the sides
if RMaxBJ(h[1],h[7]) > RMaxBJ(h[3],h[5]) then
set z = z + (RMaxBJ(h[3]-z,0.)+RMaxBJ(h[5]-z,0.))/2
set z = z + (RMaxBJ(h[1]-z,0.)+RMaxBJ(h[7]-z,0.))/2
else
set z = z + (RMaxBJ(h[1]-z,0.)+RMaxBJ(h[7]-z,0.))/2
set z = z + (RMaxBJ(h[3]-z,0.)+RMaxBJ(h[5]-z,0.))/2
endif
// do the corners
set i = 0
loop
set r = 0.
// depending on the current position, get the highest "side" mountain
if i == 0 then
set r = RMaxBJ(h[1],h[3])
elseif i == 2 then
set r = RMaxBJ(h[1],h[5])
elseif i == 6 then
set r = RMaxBJ(h[3],h[7])
elseif i == 8 then
set r = RMaxBJ(h[7],h[5])
endif
if h[i] > r and h[4] < h[i] and i != 4 then
set z = z + ((h[i]-RMaxBJ(h[4],r))/4) // calculate based on the highest point, either that at the origin, or of the side-mountain
endif
set i = i + 2
exitwhen i > 8
endloop
//
call .addZ(x,y,z)
Conclusion:
The flyheight that is automatically added on unit's with movement type "Fly" by the surrounding (that is, not only the terrain directly underneath) can be calculated with the above algorithm. Although not very elegant, it is the only way to do this. I will in the near future release a resource that can get a flying unit's added flyheight based on this algorithm.
Thanks,
iNfraNe
Last edited: