Distorting Voronoi With Perlin Noise

Viewed 573

According to this blog post, there are several ways to make voronoi cells a bit more dynamic looking. The one that I'm interested in is the first one that they mentioned:

The above illustration is the same Voronoi diagram as the one above, only now, Perlin noise has been used to distort which points belong to which cell. This creates some more interesting borders between between cells. This is kind of easy to accomplish as long as you use a pixel-by-pixel (or tile-by-tile) assignment to the closest voronoi origin, since you can quite simply offset the actual coordinates of the pixel by Perlin noise - quite closely related to distorted Perlin noise.

I have seen similar ideas mentioned elsewhere, but not actual code showing how the perlin noise is "distorted" or "added" to the voronoi diagram. I've tried applying it through guessing, but have had no luck. The way my code is written, the distance between points is in the hundreds, while the perlin noise value is only from 0 to 1, so adding or subtracting the noise really doesn't do much. Multiplying just seems to break the voronoi. I've tried scaling the voronoi distance values to be between 0 and 1 or -1 to 1 and then applying to noise, but that didn't work either.

Below is an example of the voronoi diagram and perlin noise I am generating. I would appreciate any feedback or ability to point me in the right direction.

from PIL import Image
import random
import math
import numpy as np
import noise
wid = 500
hei = 250
image = Image.new("RGB",(wid,hei))
world_test = np.zeros(image.size)
scale       = 100 # Number that determines at what distance to view the noisemap
octaves     = 6 # the number of levels of detail you want you perlin noise to have
persistence = 0.5 # number that determines how much detail is added or removed at each octave (adjusts frequency)
lacunarity  = 2.0 # number that determines how much each octave contributes to the overall shape (adjusts amplitude)
# Creates perlin noise
for x in range(wid):
    for y in range(hei):
        world_test[x][y] = ((noise.pnoise2(x/100, 
                                    y/100, 
                                    octaves     = octaves, 
                                    persistence = persistence, 
                                    lacunarity  = lacunarity, 
                                    repeatx     = wid, 
                                    repeaty     = hei, 
                                    base        = 0)))
def generate_voronoi_diagram(width, height, num_cells):
    image = Image.new("RGB", (width, height))
    putpixel = image.putpixel
    imgx, imgy = image.size
    nx = []
    ny = []
    nr = []
    ng = []
    nb = []
    #Go through number of cells
    for i in range(num_cells):
        #creat a point (x,y) and give it a specific color value
        nx.append(random.randrange(imgx))
        ny.append(random.randrange(imgy))
        nr.append(random.randrange(256))
        ng.append(random.randrange(256))
        nb.append(random.randrange(256))
    #go through each pixel in the image
    for y in range(int(imgy)):
        for x in range(int(imgx)):
            dmin = math.hypot(imgx-1, imgy-1)
            j = -1
            #go through each cell
            for i in range(num_cells):
                # d is distance from each voronoi starting point
                d = math.hypot((nx[i]-x), (ny[i]-y))
                # apply perlin distort to d
                d +=  world_test[x][y]
                #if distance is less than the current min distance,
                #set that point as the owner of this pixel and the new dmin
                if d < dmin:
                    dmin = d
                    j = i
            putpixel((x, y), (nr[j], ng[j], nb[j]))
    image.save("Voronoi_example.png", "PNG")
    image.show()
generate_voronoi_diagram(wid, hei, 30)
1 Answers

I think I found a solution to this question. If you create separated noise maps for each coordinate(x and y) and add the value of that noise maps to each pixel during the calculation of d this value will get distorted by the maps. I think I found this answer on this site but i'm not sure: https://gamedev.stackexchange.com/questions/182582/how-to-distort-an-image-using-perlin-noise

Based on this site I modified your python code and it now should work.

from PIL import Image
import random
import math
import numpy as np
import noise
wid = 100
hei = 100
image = Image.new("RGB",(wid,hei))
world_test_x = np.zeros(image.size)
world_test_y = np.zeros(image.size)
scale       = 0.1 # Number that determines at what distance to view the noisemap
octaves     = 6 # the number of levels of detail you want you perlin noise to have
persistence = 0.5 # number that determines how much detail is added or removed at each octave (adjusts frequency)
lacunarity  = 2.0 # number that determines how much each octave contributes to the overall shape (adjusts amplitude)
seed = 19829813472
mult = 50 # Strenght
# Creates perlin noise to distort x coordinates
for x in range(wid):
    for y in range(hei):
        world_test_x[x][y] = ((noise.pnoise2(x/100, 
                                    y/100, 
                                    octaves     = octaves, 
                                    persistence = persistence, 
                                    lacunarity  = lacunarity, 
                                    repeatx     = wid, 
                                    repeaty     = hei, 
                                    base        = 0)))*mult
# Creates perlin noise to distort y coordinates
for x in range(wid):
    for y in range(hei):
        world_test_y[x][y] = ((noise.pnoise2((x+seed)/100, 
                                    (y+seed)/100, 
                                    octaves     = octaves, 
                                    persistence = persistence, 
                                    lacunarity  = lacunarity, 
                                    repeatx     = wid, 
                                    repeaty     = hei, 
                                    base        = 0)))*mult

def generate_voronoi_diagram(width, height, num_cells):
    image = Image.new("RGB", (width, height))
    putpixel = image.putpixel
    imgx, imgy = image.size
    nx = []
    ny = []
    nr = []
    ng = []
    nb = []
    nsize=[]
    #Go through number of cells
    for i in range(num_cells):
        #creat a point (x,y) and give it a specific color value
        nx.append(random.randrange(imgx))
        ny.append(random.randrange(imgy))
        nr.append(random.randrange(256))
        ng.append(random.randrange(256))
        nb.append(random.randrange(256))
        nsize.append(0)
    #go through each pixel in the image
    for y in range(int(imgy)):
        for x in range(int(imgx)):
            dmin = math.hypot(imgx-1, imgy-1)
            j = -1
            #go through each cell
            for i in range(num_cells):
                # d is distance from each voronoi starting point
                # each point gets its coordinates distorted so d also gets distorted
                d = math.hypot((nx[i]-x+world_test_x[x][y]), (ny[i]-y+world_test_y[x][y]))
                #if distance is less than the current min distance,
                #set that point as the owner of this pixel and the new dmin
                if d < dmin:
                    dmin = d
                    j = i
                nsize[j]+=1
            putpixel((x, y), (nr[j], ng[j], nb[j]))
    image.save("Voronoi_example.png", "PNG")
    image.show()
    print(nsize)
    print(nr)
generate_voronoi_diagram(wid, hei, 8)

Here is the Voronoi map

Here is the distorted Voronoi map

Related