pvigier / perlin-numpy

A fast and simple perlin noise generator using numpy
https://pvigier.github.io/2018/06/13/perlin-noise-numpy.html
MIT License
304 stars 50 forks source link

Does not take into account non integer divisors #3

Open philipdavis82 opened 5 years ago

philipdavis82 commented 5 years ago

I got this error when generating noise

Traceback (most recent call last):
  File "c:/Software/Test Software/PerlinNoise.py", line 93, in <module>
    noise = terrain.generate_fractal_noise_2d((100,100),(10,10),3)
  File "c:/Software/Test Software/PerlinNoise.py", line 85, in generate_fractal_noise_2d
    noise += amplitude * self.generate_perlin_noise_2d(shape, (int(frequency*res[0]), int(frequency*res[1])))
  File "c:/Software/Test Software/PerlinNoise.py", line 69, in generate_perlin_noise_2d
    n00 = np.sum(grid * g00, 2)
ValueError: operands could not be broadcast together with shapes (100,100,2) (80,80,2)

I realized it was due to this line

        delta = (res[0] / shape[0], res[1] / shape[1])
        d = (shape[0] // res[0], shape[1] // res[1])
        grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1]].transpose(1, 2, 0) % 1
        # Gradients
        angles = 2*np.pi*self.random.rand(res[0]+1, res[1]+1)
        gradients = np.dstack((np.cos(angles), np.sin(angles)))
        g00 = gradients[0:-1,0:-1].repeat(d[0], 0).repeat(d[1], 1)
        g10 = gradients[1:,0:-1].repeat(d[0], 0).repeat(d[1], 1)
        g01 = gradients[0:-1,1:].repeat(d[0], 0).repeat(d[1], 1)
        g11 = gradients[1:,1:].repeat(d[0], 0).repeat(d[1], 1)

Grid must be a integer multiple of the gradiant size when repeating the gradient or else the sizes will not line up.

for the above example the shape of the grid was (100, 100, 2) and the shape of g00 was (80, 80, 2). so they were not able to be broadcast by the np.sum function.

This was my fix

    def generate_perlin_noise_2d(self,shape, res):
        def f(t):
            return 6*t**5 - 15*t**4 + 10*t**3

        delta = (res[0] / shape[0], res[1] / shape[1])
        d = (shape[0] // res[0], shape[1] // res[1])
        grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1]].transpose(1, 2, 0) % 1
        # Gradients
        angles = 2*np.pi*self.random.rand(res[0]+1, res[1]+1)
        gradients = np.dstack((np.cos(angles), np.sin(angles)))
        if d[0]*delta[0] != 1:
            d = (d[0] + 1,d[1])
        if d[1]*delta[1] != 1:
            d = (d[0],d[1] + 1)
        g00 = gradients[0:-1,0:-1].repeat(d[0], 0).repeat(d[1], 1)
        g10 = gradients[1:,0:-1].repeat(d[0], 0).repeat(d[1], 1)
        g01 = gradients[0:-1,1:].repeat(d[0], 0).repeat(d[1], 1)
        g11 = gradients[1:,1:].repeat(d[0], 0).repeat(d[1], 1)
        # Ramps
        n00 = np.sum(grid * g00[:len(grid),:len(grid[0])], 2)
        n10 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1])) * g10[:len(grid),:len(grid[0])], 2)
        n01 = np.sum(np.dstack((grid[:,:,0], grid[:,:,1]-1)) * g01[:len(grid),:len(grid[0])], 2)
        n11 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1]-1)) * g11[:len(grid),:len(grid[0])], 2)
        # Interpolation
        t = f(grid)
        n0 = n00*(1-t[:,:,0]) + t[:,:,0]*n10
        n1 = n01*(1-t[:,:,0]) + t[:,:,0]*n11

        return np.sqrt(2)*((1-t[:,:,1])*n0 + t[:,:,1]*n1)

The idea was just to repeat one more time then slice the matrix for the sum. This way any number should be able to be entered and there are still no loops.

I don't know if this is even an issue for most people, I just wanted to put it here.

pvigier commented 5 years ago

Thanks for sharing your improvement!

May I make some remarks:

If you want you can make a PR.

Best, Pierre