perlin-numpy
                                
                                
                                
                                    perlin-numpy copied to clipboard
                            
                            
                            
                        Does not take into account non integer divisors
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.
Thanks for sharing your improvement!
May I make some remarks:
- Instead of using two 
ifs, I think you can use theceilfunction in the declaration ofdlike that: 
d = np.ceil([shape[0] / res[0], shape[1] / res[1]]).astype(int)
- I find that it is clearer if the slicing is done directly in the declaration of gradients like that:
 
g00 = gradients[0:-1,0:-1].repeat(d[0], 0).repeat(d[1], 1)[:shape[0],:shape[1])]
If you want you can make a PR.
Best, Pierre