I am doing some floating point calculations and the results are not as accurate as I want them to be.
This is the algorithm:
...
center = (max_x + min_x) / 2
distance = old_x - center
new_x = center + (distance * factor)
return new_x
min_x, max_x, and old_x are all floats.
I believe that the greatest error is introduced when I'm taking the average of the max and the min, and then the error is multiplied by the factor (which can be a float).
How can I minimize the error due to FP computation so that new_x is as precise as it can be?
If old_x and center are close then you're losing precision.
It's called Loss of significance
You could change the calculation so the subtraction happenS in the end:
center = (max_x + min_x) / 2
new_x = (center + (old_x * factor)) - (center * factor)
Depending on your language, there is probably a fixed/arbitrary precision numeric type you can use such as decimal in python or BigDecimal in Java.
This eliminates at least one source of error from your original algorithm:
# Adding min and max can produce a value of larger magnitude, losing some low-order bits
center = min_x + (max_x - min_x)/2
distance = old_x - center
new_x = center + (distance * factor)
return new_x
If you have more knowledge of the relationship between old_x, min_x andmax_x, you can probably do better than this.
As Yochai says, your problem is probably caused by the subtraction old_x - center. If old_x and center are close to each other then you lose precision.
The simple solution would be do to the computation using double instead of float, but I guess that's not possible. In that case, you need to get rid of the subtraction. One possibility is
distance_max = max_x - center
distance_min = min_x - center
distance = (distance_max + distance_min) / 2
new_x = center + factor * distance
This helps if max_x, min_x and center are quite far apart while the average of max_x and min_x is close to center. If that does not help, perhaps you can adapt the computation of max_x so that you actually compute max_x - center but that needs changes in the part you did not show us.
All the previous implementations do not use rounding and thus have a large error:
Here's how to do this in fixed point math:
I'm using X.1u prevision (1 LSB is used for fraction part).
//center = (max_x + min_x) / 2
center = max_x + min_x // zero error here
// distance = old_x - center
distance = (old_x << 1) - center // zero error here
//new_x = center + (distance * factor)
new_x = (**1** + center + (distance * factor)) >> 1
return new_x
If factor is a fixed point (integer) too with N bits describing the fraction then new_x can be calculated as:
new_x = ( (1 << N) + (center << N) + (distance * factor) ) >> (N + 1)
(center << N) has N+1 fraction bits
distance * factor has N+1 fraction bits
(1 << N) is a 'half' as 1 << (N+1) is 'one' in the above fixed point precision.
After understanding each part, the above line can be compacted:
new_x = ( ((1 + center) << N) + (distance * factor) ) >> (N + 1)
The used integer type should be large enough, off course. If the valid range is unknown, one should check the input to this function and something else. In most cases this isn't needed.
This is as good as it get in fixed point math. This is how HW circuits perform integer math operations.
Related
I am using the diamond-square algorithm to generate random terrain.
It works fine except I get these large cone shapes either sticking out of or into the terrain.
The problem seems to be that every now and then a point gets set either way too high or way too low.
Here is a picture of the problem
And it can be better seen when I set the smoothness really high
And here is my code -
private void CreateHeights()
{
if (cbUseLand.Checked == false)
return;
int
Size = Convert.ToInt32(System.Math.Pow(2, int.Parse(tbDetail.Text)) + 1),
SideLength = Size - 1,
d = 1025 / (Size - 1),
HalfSide;
Heights = new Point3D[Size, Size];
float
r = float.Parse(tbHeight.Text),
Roughness = float.Parse(RoughnessBox.Text);
//seeding all the points
for (int x = 0; x < Size; x++)
for (int y = 0; y < Size; y++)
Heights[x, y] = Make3DPoint(x * d, 740, y * d);
while (SideLength >= 2)
{
HalfSide = SideLength / 2;
for (int x = 0; x < Size - 1; x = x + SideLength)
{
for (int y = 0; y < Size - 1; y = y + SideLength)
{
Heights[x + HalfSide, y + HalfSide].y =
(Heights[x, y].y +
Heights[x + SideLength, y].y +
Heights[x, y + SideLength].y +
Heights[x + SideLength, y + SideLength].y) / 4 - r + ((float)(random.NextDouble() * r) * 2);
}
}
for (int x = 0; x < Size - 1; x = x + SideLength)
{
for (int y = 0; y < Size - 1; y = y + SideLength)
{
if (y != 0)
Heights[x + HalfSide, y].y = (Heights[x, y].y + Heights[x + SideLength, y].y + Heights[x + HalfSide, y + HalfSide].y + Heights[x + HalfSide, y - HalfSide].y) / 4 - r + ((float)(random.NextDouble() * r) * 2);
if (x != 0)
Heights[x, y + HalfSide].y = (Heights[x, y].y + Heights[x, y + SideLength].y + Heights[x + HalfSide, y + HalfSide].y + Heights[x - HalfSide, y + HalfSide].y) / 4 - r + ((float)(random.NextDouble() * r) * 2);
}
}
SideLength = SideLength / 2;
r = r / Roughness;
}
}
Gavin S. P. Miller gave a SIGGRAPH '86 talk about how Fournier, Fussel & Carpenter's original algorithm was fundamentally flawed. So what you're seeing is normal for any naive implementation of the Diamond Square algorithm. You will require a separate approach for smoothing, either post each Diamond-Square compound step, or as a post-process to all diamond-square iterations (or both). Miller addressed this. Weighting and box or gaussian filtering are one option; seeding the initial array to a greater degree than just the initial 4 points (i.e., replicating the resultsets of the first few steps of diamond-square either manually or using some built-in intelligence, but instead supplying unbiased values); the more initial information you give the array before increasing the detail using diamond-square, the better your results will be.
The reason appears to be in how the Square step is performed. In the Diamond step, we've taken the average of the four corners of a square to produce that square's centre. Then, in the subsequent Square step, we take the average of four orthogonally-adjacent neighbours, one of which is the square's centre point we just produced. Can you see the problem? Those original corner height values are contributing too much to the subsequent diamond-square iteration, because they are contributing both through their own influence AND through the midpoint that they created. This causes the spires (extrusive and intrusive), because locally-derived points tend more strongly toward those early points... and because (typically 3) other points do as well, this creates "circular" influences around those points, as you iterate to higher depths using Diamond-Square. So these kinds of "aliasing" issues only appear when the initial state of the array is underspecified; in fact, the artifacting that occurs can be seen as a direct geometric consequence of using only 4 points to start with.
You can do one of the following:
Do local filtering -- generally expensive.
Pre-seed the initial array more thoroughly -- requires some intelligence.
Never smooth too many steps down from a given set of initial points -- which applies even if you do seed the initial array, it's all just a matter of relative depths in conjunction with your own maximum displacement parameters.
I believe the size of the displacement r in each iteration should be proportional to the size of the current rectangle. The logic behind this is that a fractal surface is scale invariant, so the variation in height in any rectangle should be proportional to the size of that rectangle.
In your code, the variation in height is proportional to r, so you should keep it proportional to the size of your current grid size. In other words: multiply r by the roughness before the loop and divide r by 2 in each iteration.
So, instead of
r = r / Roughness;
you should write
r = r / 2;
The actual flaw in the above algorithm is an error in conceptualization and implementation. Diamond square as an algorithm has some artifacting but this is range based artifacts. So the technical max for some pixels is higher than some other pixels. Some pixels are directly given values by the randomness while others acquire their values by the diamond and squared midpoint interpolation processes.
The error here is that you started from zero. And repeatedly added the value to the current value. This causes the range of diamond squared to start at zero and extend upwards. It must actually start at zero and go both up and down depending on the randomness. So the top range thing won't matter. But, if you don't realize this and naively implement everything as added to the value, rather than starting at zero and fluctuating from there, you will expose the hidden artifacts.
Miller's notes were right, but the flaw is generally hidden within the noise. This implementation is shows those problems. That is NOT normal. And can be fixed a few different ways. This was one of the reasons why after I extended this algorithm to remove all the memory restrictions and size restrictions and made it infinite and deterministic1, I then still switched away from the core idea here (the problems extending it to 3d and optimizing for GPUs also played a role.2
Instead of just smoothening with an average, you can use a 2-D median filter to take out extremes. It is simple to implement, and usually generates the desired effect with a lot of noise.
I'm working with Perlin Noise for a height map generation algorithm, I would like to make it wrap around edges so that it can been seen as continuous.. is there a simple way or trick to do that? I guess I need something like a spherical noise so that either horizontally and vertically it wraps around. I would be happy also with just 1 wrapping axis but two would be better.
For now I'm using the classical algorithm in which you can set up how many octaves you want to add and which are the multipliers used for changing amplitude and frequency of the waves between every successive octave.
Thanks in advance!
Perlin noise is obtained as the sum of waveforms. The waveforms are obtained by interpolating random values, and the higher octave waveforms have smaller scaling factors whereas the interpolated random values are nearer to each other. To make this wrap around, you just need to properly interpolate around the y- and x-axes in the usual toroidal fashion, i.e. if your X-axis spans from x_min to x_max, and the leftmost random point (which is being interpolated) is at x0 and the rightmost at x1 (x_min < x0 < x1 < x_max), the value for the interpolated pixels right to x1 and left from x0 are obtained by interpolating from x1 to x0 (wrapping around the edges).
Here pseudocode for one of the octaves using linear interpolation. This assumes a 256 x 256 matrix where the Perlin noise grid size is a power of two pixels... just to make it readable. Imagine e.g. size==16:
wrappable_perlin_octave(grid, size):
for (x=0;x<256;x+=size):
for (y=0;y<256;y+=size):
grid[x][y] = random()
for (x=0;x<256;x+=size):
for (y=0;y<256;y+=size):
if (x % size != 0 || y % size != 0): # interpolate
ax = x - x % size
bx = (ax + size) % 256 # wrap-around
ay = y - y % size
by = (ay + size) % 256 # wrap-around
h = (x % size) / size # horizontal balance, floating-point calculation
v = (y % size) / size # vertical balance, floating-point calculation
grid[x][y] = grid[ax][ay] * (1-h) * (1-v) +
grid[bx][ay] * h * (1-v) +
grid[ax][by] * (1-h) * v +
grid[bx][by] * h * v
For a square grid the euclidean distance between tile A and B is:
distance = sqrt(sqr(x1-x2)) + sqr(y1-y2))
For an actor constrained to move along a square grid, the Manhattan Distance is a better measure of actual distance we must travel:
manhattanDistance = abs(x1-x2) + abs(y1-y2))
How do I get the manhattan distance between two tiles in a hexagonal grid as illustrated with the red and blue lines below?
I once set up a hexagonal coordinate system in a game so that the y-axis was at a 60-degree angle to the x-axis. This avoids the odd-even row distinction.
(source: althenia.net)
The distance in this coordinate system is:
dx = x1 - x0
dy = y1 - y0
if sign(dx) == sign(dy)
abs(dx + dy)
else
max(abs(dx), abs(dy))
You can convert (x', y) from your coordinate system to (x, y) in this one using:
x = x' - floor(y/2)
So dx becomes:
dx = x1' - x0' - floor(y1/2) + floor(y0/2)
Careful with rounding when implementing this using integer division. In C for int y floor(y/2) is (y%2 ? y-1 : y)/2.
I assume that you want the Euclidean distance in the plane between the centers of two tiles that are identified as you showed in the figure. I think this can be derived from the figure. For any x and y, the vector from the center of tile (x, y) to the center of tile (x + dx, y) is (dx, 0). The vector from the center of tile (x, y) and (x, y + dy) is (-dy / 2, dy*sqrt(3) / 2). A simple vector addition gives a vector of (dx - (dy / 2), dy * sqrt(3) / 2) between (x, y) and (x + dx, y + dy) for any x, y, dx, and dy. The total distance is then the norm of the vector: sqrt((dx - (dy / 2)) ^ 2 + 3 * dy * dy / 4)
If you want the straight-line distance:
double dy = y2 - y1;
double dx = x2 - x1;
// if the height is odd
if ((int)dy & 1){
// whether the upper x coord is displaced left or right
// depends on whether the y1 coordinate is odd
dx += ((y1 & 1) ? -0.5 : 0.5);
}
double dis = sqrt(dx*dx + dy*dy);
What I'm trying to say is, if dy is even, it's just a rectangular space. If dy is odd, the position of the upper right corner is 1/2 unit to the left or to the right.
A straight forward answer for this question is not possible. The answer of this question is very much related to how you organize your tiles in the memory. I use odd-q vertical layout and with the following matlab code gives me the right answer always.
function f = offset_distance(x1,y1,x2,y2)
ac = offset_to_cube(x1,y1);
bc = offset_to_cube(x2,y2);
f = cube_distance(ac, bc);
end
function f = offset_to_cube(row,col)
%x = col - (row - (row&1)) / 2;
x = col - (row - mod(row,2)) / 2;
z = row;
y = -x-z;
f = [x,z,y];
end
function f= cube_distance(p1,p2)
a = abs( p1(1,1) - p2(1,1));
b = abs( p1(1,2) - p2(1,2));
c = abs( p1(1,3) - p2(1,3));
f = max([a,b,c]);
end
Here is a matlab testing code
sx = 6;
sy = 1;
for i = 0:7
for j = 0:5
k = offset_distance(sx,sy,i,j);
disp(['(',num2str(sx),',',num2str(sy),')->(',num2str(i),',',num2str(j),')=',num2str(k)])
end
end
For mathematical details of this solution visit: http://www.redblobgames.com/grids/hexagons/ . You can get a full hextile library at: http://www.redblobgames.com/grids/hexagons/implementation.html
This sounds like a job for the Bresenham line algorithm. You can use that to count the number of segments to get from A to B, and that will tell you the path distance.
If you define the different hexagons as a graph, you can get the shortest path from node A to node B. Since the distance from the hexagon centers is constant, set that as the edge weight.
This will probably be inefficient for large fields though.
I'm trying to figure out an algorithm for finding a random point a set distance away from a base point. So for example:
This could just be basic maths and my brain not working yet (forgive me, haven't had my coffee yet :) ), but I've been trying to work this out on paper and I'm not getting anywhere.
coordinate of point on circle with radius R and center (xc, yc):
x = xc + R*cos(a);
y = yc + R*sin(a);
changing value of angle a from 0 to 2*PI you can find any point on circumference.
Use the angle from the verticle as your random input.
Pseudocode:
angle = rand(0,1)
x = cos(angle * 2 * pi) * Radius + x_centre
y = sin(angle * 2 * pi) * Radius + y_centre
Basic Pythagoras.
Pick random number between 0 and 50 and solve h^2 = a^2 + b^2
Add a few random descisions on direction.
I have a toroidal-ish Euclidean-ish map. That is the surface is a flat, Euclidean rectangle, but when a point moves to the right boundary, it will appear at the left boundary (at the same y value), given by x_new = x_old % width
Basically, points are plotted based on: * see edit
(x_new, y_new) = ( x_old % width, y_old % height)
Think Pac Man -- walking off one edge of the screen will make you appear on the opposite edge.
What's the best way to calculate the shortest distance between two points? The typical implementation suggests a large distance for points on opposite corners of the map, when in reality, the real wrapped distance is very close.
The best way I can think of is calculating Classical Delta X and Wrapped Delta X, and Classical Delta Y and Wrapped Delta Y, and using the lower of each pair in the Sqrt(x^2+y^2) distance formula.
But that would involve many checks, calculations, operations -- some that I feel might be unnecessary.
Is there a better way?
edit
When an object moves, it moves to position (x_old,y_old), runs it through the above formula, and stores (x_new, y_new) as its position. The above formula was only added to clarify what happens when objects move across the boundary; in reality, only one (x,y) pair is stored in each object at a time.
The best way I can think of is calculating Classical Delta X and Wrapped Delta X, and Classical Delta Y and Wrapped Delta Y, and using the lower of each pair in the Sqrt(x^2+y^2) distance formula.
That's it, I don't think there is any quicker way. But it's not too hard of a computation; you could do something like
dx = abs(x1 - x2);
if (dx > width/2)
dx = width - dx;
// again with x -> y and width -> height
(I trust you can translate that into your preferred language)
The shortest distance between two points in a periodic domain can be computed as follows without using any loops.
dx = x2-x1
dx = dx - x_width*ANINT(dx/x_width)
This will give a signed shortest distance. ANINT is an intrinsic FORTRAN function such that ANINT(x) gives the nearest whole number whose magnitude is less than abs(x)+0.5, with the same sign as x. For eg., ANINT(0.51)=1.0 , ANINT(-0.51)=-1.0 , etc. Similar functions exist for other languages.
To find the smallest delta in the a-axis for new coordinates with values a1 and a2, where aBoundaryis the boundary on the a-axis:
def delta(a1, a2, aBoundary):
return min(abs(a2 - a1), abs(a2 + aBoundary - a1))
So if you have two points with new coordinates x1,y1 and x2,y2, you can just do:
sumOfSquares(delta(x1,x2,width), delta(y1,y2,height))
This is effectively what you suggest, but I wouldn't say it's "many checks, calculations and operations".
No distance can be greater than width/2 and height/2. If you get a difference (X1-X2) greater than width/2, substract width/2 to get the short-cut distance. Calculate distance then as usual.
(delta_x, delta_y)=
(min(width - abs(x_new - x_new), abs(x_new - x_old)),
min(height - abs(y_new - y_old), abs(y_new - y_old)))
Man I did something WAY different...
there's a little extra fuctionality in here but the core is the distance on a wrapped screen...
from math import sqrt
import pytweening
class ClosestPoint_WD(object):
def __init__(self, screen_size, point_from, point_to):
self._width = screen_size[0]
self._height = screen_size[1]
self._point_from = point_from
self._point_to = point_to
self._points = {}
self._path = []
def __str__(self):
value = "The dictionary:" + '\n'
for point in self._points:
value = value + str(point) + ":" + str(self._points[point]) + '\n'
return value
def distance(self, pos0, pos1):
dx = pos1[0] - pos0[0]
dy = pos1[1] - pos0[1]
dz = sqrt(dx**2 + dy**2)
return dz
def add_point_to_dict(self, x, y):
point = x, y
self._points[point] = 0
def gen_points(self):
max_x = self._width * 1.5 - 1
max_y = self._height * 1.5 - 1
# point 1, original point
self.add_point_to_dict(self._point_to[0], self._point_to[1])
# add the second point: x-shifted
if self._point_to[0] + self._width <= max_x:
self.add_point_to_dict(self._point_to[0] + self._width, self._point_to[1])
else:
self.add_point_to_dict(self._point_to[0] - self._width, self._point_to[1])
# add the third point: y-shifted
if self._point_to[1] + self._height <= max_y:
self.add_point_to_dict(self._point_to[0], self._point_to[1] + self._height)
else:
self.add_point_to_dict(self._point_to[0], self._point_to[1] - self._height)
# add the fourth point: diagonally shifted
if self._point_to[0] + self._width <= max_x:
if self._point_to[1] + self._height <= max_y:
self.add_point_to_dict(self._point_to[0] + self._width, self._point_to[1] + self._height)
else:
self.add_point_to_dict(self._point_to[0] + self._width, self._point_to[1] - self._height)
else:
if self._point_to[1] + self._height <= max_y:
self.add_point_to_dict(self._point_to[0] - self._width, self._point_to[1] + self._height)
else:
self.add_point_to_dict(self._point_to[0] - self._width, self._point_to[1] - self._height)
def calc_point_distances(self):
for point in self._points:
self._points[point] = self.distance(self._point_from, point)
def closest_point(self):
d = self._points
return min(d, key=d.get)
def update(self, cur_pos, target):
self._point_from = cur_pos
self._point_to = target
self._points = {}
self.gen_points()
self.calc_point_distances()
self.shortest_path()
def shortest_path(self):
path = pytweening.getLine(self._point_from[0], self._point_from[1], self.closest_point()[0], self.closest_point()[1])
#path = pytweening.getLine((self._point_from)
ret_path = []
for point in path:
ret_path.append((point[0] % self._width, point[1] % self._height))
self._path = ret_path
return self._path
you can't use the “abs” function with the mod operator!
xd =(x1-x2+Width)%Width
yd=(y1-y2+Height)%Height
D=sqrt(xd^2+yd^2)