Python Diamond Square Algorithm Implementation - python

I have attempted creating a diamond square algorithm based on this javascript because it is readable and makes sense too me. I am having a few issues that I cannot seem to solve however.
When running the code, the desired output is some random value populating each position of the 2D array that is different from the initial array initialization. The issue that I am having is that the result 2D array isn't being completely populated and when increasing the grid size above 3, I get a IndexError: list index out of range error.
Here is the code:
class DiamondSquare:
def __init__(self, size, roughness):
self.size = (size ** 2) + 1
self.max = self.size - 1
self.roughness = roughness
self.grid = self.make_grid(self.size)
self.divide(self.max)
print(self.grid)
def divide(self, size):
x = size / 2
y = size / 2
half = size / 2
scale = self.roughness * size
if (half < 1):
return
# Squares
for y in range(half, self.max, size):
for x in range(half, self.max, size):
s_scale = random.uniform(0, 1) * scale * 2 - scale
self.square(x, y, half, s_scale)
# Diamonds
for y in range(0, self.max, half):
for x in range((y + half) % size, self.max, size):
d_scale = random.uniform(0, 1) * scale * 2 - scale
self.diamond(x, y, half, d_scale)
self.divide(size / 2)
def square(self, x, y, size, scale):
"""
TL TR
X
BL BR
"""
tl = self.grid[x - size][y - size]
tr = self.grid[x + size][y - size]
bl = self.grid[x + size][y + size]
br = self.grid[x - size][y + size]
average = ((tl + tr + bl + br) / 4) + scale
self.grid[x][y] = average
def diamond(self, x, y, size, scale):
"""
T
L X R
B
"""
t = self.grid[x][y - size]
r = self.grid[x + size][y]
b = self.grid[x][y + size]
l = self.grid[x - size][y + size]
average = ((t + r + b + l) / 4) + scale
self.grid[x][y] = average
def make_grid(self, size):
grid = []
for y in range(size):
temp = []
for x in range(size):
temp.append(-1)
grid.append(temp)
grid[0][0] = self.max
grid[self.max][0] = self.max / 2
grid[self.max][self.max] = 0
grid[0][self.max] = self.max / 2
return grid
def get_grid(self):
return self.grid
When trying to increase the size variable (in init) to a value above 2, I get the following traceback:
Traceback (most recent call last):
File "C:\Users\user\Documents\Development\Python\Fractal\diamond_square.py", line 150, in <module>
a = DiamondSquare(5, 0.7)
File "C:\Users\user\Documents\Development\Python\Fractal\diamond_square.py", line 14, in __init__
self.divide(self.max)
File "C:\Users\user\Documents\Development\Python\Fractal\diamond_square.py", line 35, in divide
self.diamond(x, y, half, d_scale)
File "C:\Users\user\Documents\Development\Python\Fractal\diamond_square.py", line 68, in diamond
r = self.grid[x + size][y]
IndexError: list index out of range
I am honestly not sure why this is happening and I can't figure it out at all. I am using the following code to produce this error:
a = DiamondSquare(x, 0.7)
Where x is any integer above 2 and the second parameter is the roughness.
Also regarding the grid error, trying to create a grid from DiamondSquare.divide(), produces the following:
[[4, 1.0649105908291359, 1.234026481506731, 0.07818244918327344, 2],
[0.43855022217756057, 0.4659935454877355, 1.283183468707215, 0.28019876872734906, -1],
[-0.4946413746345607, -1.1327574166276582, 0.45804405178511276, -1.4905717022572778, -1],
[-1.4175095415414622, -0.660055583070249, -0.8017056243549873, -0.18216161649389495, -1],
[2, -1, -1, -1, 0]]
Where the -1's are, there should be other random numbers as with the rest of the grid. The -1's make up the mid point of the bottom and the right hand side. I believe it is something to do with my diamonds for loop but I am not sure where I am going wrong.
I achieve this grid, I use the following code:
a = DiamondSquare(2, 0.7)
where the first parameter is the size and the second is the roughness.
Could I please have some help resolving the issues stated above? Thank you in advance!

You changed the place of the base and the power when you create the size parameter. You wrote (size ** 2) + 1 but it should be (2 ** size) + 1. This will hopefully solve your problems.

I managed to solve it by removing the 2D list and simply using a 1D list instead which is what I should have done in the first place but I didn't because I misread the original code.
class DiamondSquare:
def __init__(self, size, roughness):
self.size = (2 ** size) + 1
self.max = self.size - 1
self.roughness = roughness
self.make_grid(self.size)
self.divide(self.max)
# Sets x,y position in self.grid
def set(self, x, y, val):
self.grid[x + self.size * y] = val;
# Get's value of x, y in self.grid
def get(self, x, y):
if (x < 0 or x > self.max or y < 0 or y > self.max):
return -1
return self.grid[x + self.size * y]
def divide(self, size):
x = size / 2
y = size / 2
half = size / 2
scale = self.roughness * size
if (half < 1):
return
# Square
for y in range(half, self.max, size):
for x in range(half, self.max, size):
s_scale = random.uniform(0, 1) * scale * 2 - scale
self.square(x, y, half, s_scale)
# Diamond
for y in range(0, self.max + 1, half):
for x in range((y + half) % size, self.max + 1, size):
d_scale = random.uniform(0, 1) * scale * 2 - scale
self.diamond(x, y, half, d_scale)
self.divide(size / 2)
def square(self, x, y, size, scale):
top_left = self.get(x - size, y - size)
top_right = self.get(x + size, y - size)
bottom_left = self.get(x + size, y + size)
bottom_right = self.get(x - size, y + size)
average = ((top_left + top_right + bottom_left + bottom_right) / 4)
self.set(x, y, average + scale)
def diamond(self, x, y, size, scale):
"""
T
L X R
B
"""
top = self.get(x, y - size)
right = self.get(x + size, y)
bottom = self.get(x, y + size)
left = self.get(x - size, y)
average = ((top + right + bottom + left) / 4)
self.set(x, y, average + scale)
def make_grid(self, size):
self.grid = []
for x in range(size * size):
self.grid.append(-1)
self.set(0, 0, self.max)
self.set(self.max, 0, self.max /2 )
self.set(self.max, self.max, 0)
self.set(0, self.max, self.max / 2)
def get_grid(self):
return self.grid
a = DiamondSquare(3, 0.5)
print(a.get_grid())

Related

Perlin noise generator isn't working, doesn't look smooth

I watched some tutorials and tried to create a Perlin noise generator in python.
It takes in a tuple for the number of vectors in the x and y directions and a scale for the distance in pixels between the arrays, then calculates the dot product between each pixel and each of the 4 arrays surrounding it, It then interpolates them bilinearly to get the pixel's value.
here's the code:
from PIL import Image
import numpy as np
scale = 16
size = np.array([8, 8])
vectors = []
for i in range(size[0]):
for j in range(size[1]):
rand = np.random.rand() * 2 * np.pi
vectors.append(np.array([np.cos(rand), np.sin(rand)]))
interpolated_map = np.zeros(size * scale)
def interpolate(x1, x2, w):
t = (w % scale) / scale
return (x2 - x1) * t + x1
def dot_product(a, b):
return a[0] * b[0] + a[1] * b[1]
for i in range(size[1] * scale):
for j in range(size[0] * scale):
dot_products = []
for m in range(4):
corner_vector_x = round(i / scale) + (m % 2)
corner_vector_y = round(j / scale) + int(m / 2)
x = i - corner_vector_x * scale
y = j - corner_vector_y * scale
if corner_vector_x >= size[0]:
corner_vector_x = 0
if corner_vector_y >= size[1]:
corner_vector_y = 0
corner_vector = vectors[corner_vector_x + corner_vector_y * (size[0])]
distance_vector = np.array([x, y])
dot_products.append(dot_product(corner_vector, distance_vector))
x1 = interpolate(dot_products[0], dot_products[1], i)
x2 = interpolate(dot_products[2], dot_products[3], i)
interpolated_map[i][j] = (interpolate(x1, x2, j) / 2 + 1) * 255
img = Image.fromarray(interpolated_map)
img.show()
I'm getting this image:
but I should be getting this:
I don't know what's going wrong, I've tried watching multiple different tutorials, reading a bunch of different articles, but the result is always the same.

projecting points onto a parallel grid at a depth from camera (maya)

I am attempting to create a grid of locators that serve as projected points onto a parallel finite plane from a camera in maya at a specified depth. The grid should line up with a specified resolution so as to match rendered output.
At the moment my calculations are off and I am looking for some help to ascertain how my formula for ascertaining the projected points is incorrect.
I have a self contained python script and image showing the current position of locators that are spawned as an example.
image showing current spawned locators are off on y and z axis
import maya.cmds as mc
import maya.OpenMaya as om
res = [mc.getAttr('defaultResolution.width'),
mc.getAttr('defaultResolution.height')]
print res
grid = [5, 5]
def projectedGridPoint(camera, coord, depth, res):
selList = om.MSelectionList()
selList.add(camera)
dagPath = om.MDagPath()
selList.getDagPath(0,dagPath)
dagPath.extendToShape()
camMtx = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
mFloatMtx = fnCam.projectionMatrix()
projMtx = om.MMatrix(mFloatMtx.matrix)
#center of camera
eyePt = fnCam.eyePoint()
#offset position
z = eyePt.z - depth
#calculated xy positions
x = (2 * z * coord[0] / res[0]) - z
y = (2 * z * coord[1] / res[1]) - z
return om.MPoint(x,y,depth) * camMtx * projMtx.inverse()
for y in range(grid[1] + 1):
for x in range(grid[0] + 1):
coord = ( x / float(grid[0]) * res[0], y / float(grid[1]) * res[1] )
pt = projectedGridPoint('camera1', coord, 10, res)
mc.spaceLocator(a=1, p=[pt.x, pt.y, pt.z])
Once I adjusted Theodox's answer to account for all possible grid divisions, such that the ndc_x and ndc_y was always in the range of -1 and 1. I was able to get a working solution.
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (6, 6)
for y in range(yy + 1):
for x in range(xx + 1):
ndc_x = -1
ndc_y = -1
if x > 0:
ndc_x = (x / float(xx) * 2) - 1
if y > 0:
ndc_y = (y / float(yy) * 2) - 1
coord = ( ndc_x, ndc_y)
print coord
pt = projectedGridPoint('camera1', coord, 0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
I think you want something a bit more like this (note, i converted it to API 2 to cut down on the boilerplate)
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (2, 2)
for y in range(yy):
for x in range(xx):
ndc_x = 2.0 * x / float(xx - 1) - 1
ndc_y = 2.0 * y / float(yy - 1) - 1
coord = ( ndc_x, ndc_y)
pt = projectedGridPoint('camera1', coord,0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
The coords are supplied as normalized device coordinates (from -1,-1 to 1, 1 at the corners of the view) and the depth goes from the near to far clip planes -- a depth of 1 is right on the near plane and a depth of 0 is on the far plane. I think in practice I'd lock the depth at 0 and use the clip plane setting on the camera to set the depth
edit I rationalized the original, wonky method of converting index values to NDC coordinates

Perlin noise looks streaky and not coherent

Now that my perlin generator is 'working' I created noise, to find that it is nothing like what I see on the internets...
My noise:
Notice the streaks:
What I am aiming to get (obviously with corresponding colour):
1:
Why does mine look so noisy and nasty?
Code (sorry for no stub, the Perlin noise makes up most of the program so it's important to include the full program):
from PIL import Image
from tkinter import filedialog
from random import randint, random
#Initialise width / height
width = 625
height = 625
#Import gradient picture - 200*1 image used to texture perlin noise
#R,G,B,Alpha
gradient = Image.open("image.png")
gradlist = list(gradient.getdata())
#Create new image
img = Image.new('RGBA', (width, height), color=(255, 255, 255, 255))
#Perlin noise modules --------------------------------------------------------------------------------------------------------
#Modules
from random import sample
from math import floor
p = sample([x for x in range(0, (width * height))], (width * height)) * 2
#Antialising
def fade(t):
retval = 6*(t**5) - 15*(t**4) + 10*(t**3)
return retval
#Linear interpolation
def lerp(t,a,b):
retval = a + (t * (b - a))
return retval
#Clever bitwise hash stuff - picks a unit vector from 12 possible - (1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),(1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),(0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)
def grad(hash, x, y, z):
h = hash % 15
if h < 8:
u = x
else:
u = y
if h < 4:
v = y
elif h in (12, 14):
v = x
else:
v = z
return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
#Perlin function
def perlin(x,y,z):
ix = int(floor(x)) & 255
iy = int(floor(y)) & 255
iz = int(floor(z)) & 255
x -= int(floor(x))
y -= int(floor(y))
z -= int(floor(z))
u = fade(x)
v = fade(y)
w = fade(z)
#Complicated hash stuff
A = p[ix] + iy
AA = p[A] + iz
AB = p[A + 1] + iz
B = p[ix + 1] + iy
BA = p[B] + iz
BB = p[B + 1] + iz
return -lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),grad(p[BA], x - 1, y, z)),lerp(u, grad(p[AB], x, y - 1, z),grad(p[BB], x - 1, y - 1, z))),lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1),grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1),grad(p[BB + 1], x - 1, y - 1, z - 1))))
def octavePerlin(x,y,z,octaves,persistence):
total = 0
frequency = 1
amplitude = 1
maxValue = 0
for x in range(octaves):
total += perlin(x * frequency, y * frequency, z * frequency) * amplitude
maxValue += amplitude
amplitude *= persistence
frequency *= 2
return total / maxValue
z = random()
img.putdata([gradlist[int(octavePerlin((x + random() - 0.5) / 1000, (y + random() - 0.5) / 1000, z, 4, 2) * 100 + 100)] for x in range(width) for y in range(height)])
img.save(filedialog.asksaveasfilename() + ".png", "PNG")

Perlin noise artifacts

I've taken the Wikipedia Perlin Noise Algorithm and implemented it in Python, here is the code:
import random
import math
from PIL import Image
from decimal import Decimal
IMAGE_SIZE = 200
PERLIN_RESOLUTION = 10
GRADIENT = []
for x in range(PERLIN_RESOLUTION + 1):
GRADIENT.append([])
for y in range(PERLIN_RESOLUTION + 1):
angle = random.random() * 2 * math.pi
vector = (
Decimal(math.cos(angle)),
Decimal(math.sin(angle))
)
GRADIENT[x].append(vector)
def lerp(a0, a1, w):
return (1 - w)*a0 + w*a1
def dotGridGradient(ix, iy, x, y):
dx = x - Decimal(ix)
dy = y - Decimal(iy)
return (dx*GRADIENT[iy][ix][0] + dy*GRADIENT[iy][ix][1])
def perlin(x, y):
if x > 0.0:
x0 = int(x)
else:
x0 = int(x) - 1
x1 = x0 + 1
if y > 0.0:
y0 = int(y)
else:
y0 = int(y) - 1
y1 = y0 + 1
sx = x - Decimal(x0)
sy = y - Decimal(y0)
n0 = dotGridGradient(x0, y0, x, y)
n1 = dotGridGradient(x1, y0, x, y)
ix0 = lerp(n0, n1, sx)
n0 = dotGridGradient(x0, y1, x, y)
n1 = dotGridGradient(x1, y1, x, y)
ix1 = lerp(n0, n1, sx)
value = lerp(ix0, ix1, sy)
return value
image = Image.new('RGB', (IMAGE_SIZE, IMAGE_SIZE))
pixels = image.load()
for i in range(IMAGE_SIZE):
x = Decimal(i) / IMAGE_SIZE
for j in range(IMAGE_SIZE):
y = Decimal(j) / IMAGE_SIZE
value = perlin(x * 10, y * 10)
greyscale = (value + 1) * 255 / 2
pixels[i, j] = (greyscale, greyscale, greyscale)
image.save('artifacts.png', 'PNG')
Here is the resulting image that is created by the script:
I must be missing something here, you can very clearly see the vertices. Can anyone let me know what is going wrong?
You need to use smoothstep instead of linear interpolation.
def smoothstep(a0, a1, w):
value = w*w*w*(w*(w*6 - 15) + 10)
return a0 + value*(a1 - a0)

Trouble implementing Perlin noise in python

I'm attempting to implement the algorithm for generating 2D Perlin noise here but I'm having some trouble doing it in Python (which I am relatively new to).
I was expecting the final noise values ('z' in the linked example), to be somewhere between 0.0 and 1.0, but that's not what I'm getting. My code is below, I'd really appreciate any input.
Thanks!
perlin.py:
import math
import numpy
import random
import vector as vctr
from PIL import Image
def dot(v1, v2):
"""
Returns the dot product of the two input vectors.
Args:
v1 - First vector
v2 - Second vector
Return:
Resulting dot product
"""
return (v1.x * v2.x) + (v1.y * v2.y)
def fade(t):
"""
Fade 3t^2 - 2t^3
Args:
t - Value to fade.
Return:
Faded value.
"""
return (3 * (t ** 2)) - (2 * (t ** 3))
def lerp(minVal, maxVal, term):
"""
Args:
Return:
"""
return (maxVal - minVal) * term + minVal
def generateImage(noises, file="perlin.png"):
"""
Generates a image on disc of the resulting noise values
Args:
noises (list) - 2d list of noise values
file (str) - location of file to write to
"""
pixels = numpy.zeros((height, width, 3), dtype=numpy.uint8)
for x in range(0, width):
for y in range(0, height):
rgb = 255 * noises[x][y]
pixels[x, y] = [rgb, rgb, rgb]
# Print pixels as image
img = Image.fromarray(pixels, 'RGB')
img.save(file)
# Define the noise region
width = 300
height = 300
# Column ordered array of generated gradient vectors
g = numpy.zeros((width + 1, height + 1)).tolist()
# List of final noise values
z = numpy.zeros((width, height)).tolist()
# Fill list with randomly directed unit vectors (one for each grid point)
for x in range(0, width + 1):
for y in range(0, height + 1):
randX = random.uniform(-1.0, 1.0)
randY = random.uniform(-1.0, 1.0)
length = math.sqrt(randX**2 + randY**2)
g[x][y] = vctr.vector(randX / length, randY / length)
# For each cell in the sampling space (i.e. each pixel)
for x in range(0, width):
for y in range(0, height):
# Generate random point (p) within and relative to current cell
pX = random.uniform(0.0, 1.0)
pY = random.uniform(0.0, 1.0)
# Get the gradient vectors for each cell corner
g_tl = g[x][y]
g_tr = g[x + 1][y]
g_bl = g[x][y + 1]
g_br = g[x + 1][y + 1]
# Vectors from each cell corner to the generated point
# X axis is positive going right, Y is positive going down
tl = vctr.vector(pX, pY)
tr = vctr.vector(pX - 1, pY)
bl = vctr.vector(pX, pY - 1)
br = vctr.vector(pX - 1, pY - 1)
# Dot product these vectors to get gradient values
u = dot(tl, g_tl)
v = dot(tr, g_tr)
s = dot(bl, g_bl)
t = dot(br, g_br)
# Interpolate the gradient values
sX = fade(pX)
sY = fade(pY)
a = s + (sX * (t - s))
b = u + (sX * (v - u))
value = a + (sY * (a - b))
if (value < 0.0) or (value > 1.0):
print("VALUE IS OUT OF BOUNDS??? " + str(value))
z[x][y] = value
generateImage(z)
print("Completed Perlin noise generation!")
vector.py:
class vector:
def __init__(self, x, y):
"""
Initialise a new vector in 2D space with the input X and Y values.
x: X value of vector
y: Y value of vector
"""
self.x = x
self.y = y

Categories