Skip to content

Instantly share code, notes, and snippets.

@dcousens dcousens/heightmap.py
Last active Jun 9, 2017

Embed
What would you like to do?
Prototype of the midpoint displacement algorithm, also known as the diamond square algorithm.heightmap.py creates a triangular mesh represented as a list vertices and indices from a 2d normalized array of values and some parameters. Commonly used as a heightmap.
import itertools
def get_verts_indices_for_heightmap(heightmap, stepx, stepy, minz, maxz):
""" heightmap assumed to be normalized (values between 0 and 1)
"""
height = len(heightmap)
width = len(heightmap[0])
spanz = maxz - minz
# n_heightmap = width * height
# n_tris = n_heightmap * 2
# n_indices = n_tris * 3
indices = []
verts = []
# the indices
# embarassingly parallel
for i, j in itertools.product(range(width - 1), range(height - 1))
idx = (i * width) + j
# 4 points of a grid cell
a = idx
b = idx + 1
c = idx + width
d = idx + width + 1
t1 = (a, c, b)
t2 = (b, c, d)
indices.extend(t1)
indices.extend(t2)
# the vertices
# embarassingly parallel
for i, j in itertools.product(range(width), range(height))
x = j * stepx
y = i * stepy
z = minz + (normalized_map[i][j] * spanz)
vert = (x, y, z))
verts.append(vert)
return verts, indices
import random
def mdp(base, r):
# Calculate new dimensions
n = len(base) * 2 - 1
assert(n > 2)
# Allocate
copy = [[0 for _ in range(n)] for _ in range(n)]
# Resize
# 1 0 1
# 0 0 0
# 1 0 1
for i in range(0, n, 2):
for j in range(0, n, 2):
copy[i][j] = base[i // 2][j // 2]
# Diamond algorithm
# 0 0 0
# 0 1 0
# 0 0 0
for i in range(1, n, 2):
for j in range(1, n, 2):
# get surrounding values
a = copy[i - 1][j - 1]
b = copy[i - 1][j + 1]
c = copy[i + 1][j - 1]
d = copy[i + 1][j + 1]
# average
value = (a + b + c + d) // 4
# random
value += random.randint(-r, r)
# clamp
value = max(value, 0)
value = min(value, 255)
# store
copy[i][j] = value
# Square algorithm
# 0 1 0
# 1 0 1
# 0 1 0
for i in range(n):
for j in range(i % 2 == 0, n, 2):
a = 0
b = 0
c = 0
d = 0
# get surrounding values and account for edge cases
if (i > 0): a = copy[i - 1][j]
if (j > 0): b = copy[i][j - 1]
if (i + 1 < n): c = copy[i + 1][j]
if (j + 1 < n): d = copy[i][j + 1]
# average
value = a + b + c + d
if (i == 0) or (j == 0) or (i + 1 == n) or (j + 1 == n):
value //= 3
else:
value //= 4
# random
value += random.randint(-r, r)
# clamp
value = max(value, 0)
value = min(value, 255)
# store
copy[i][j] = value
return copy
def create_noise(n):
return [[random.randint(0, 255) for _ in range(n)] for _ in range(n)]
def smoothen(base):
n = len(base)
for i in range(1, n - 1):
for j in range(1, n - 1):
base[i][j] = (
base[i - 1][j - 1] + base[i - 1][j] + base[i - 1][j + 1] +
base[i + 0][j - 1] + base[i + 0][j] + base[i + 0][j + 1] +
base[i + 1][j + 1] + base[i + 1][j] + base[i + 1][j - 1]
) // 9
def toPPM(pixels):
height = len(pixels)
width = len(pixels[0])
buf = []
buf.append("P3")
buf.append("%i %i\n%i" % (width, height, 255))
for row in pixels:
for pixel in row:
r = (pixel >> 16) & 0xFF
g = (pixel >> 8) & 0xFF
b = pixel & 0xFF
buf.append("%i %i %i" % (r, g, b))
return "\n".join(buf)
def fromPPM(text):
lines = text.splitlines()
width, height, _ = (int(x) for x in lines[1].split())
pixels = [[0 for _ in range(width)] for _ in range(height)]
lines = reverse(lines)
for row in pixels:
for i, pixel in enumerate(row)
r, g, b = (int(x) for x in lines.pop().split())
row[i] = (r << 16) & (g << 8) & b
return pixels
import json
def save(noise, name):
js = json.dumps(noise)
with open(name + ".json", "w") as fh:
fh.write(js)
ppm = toPPM(noise)
with open(name + ".ppm", "w") as fh:
fh.write(ppm)
if (__name__ == "__main__"):
noi = create_noise(32)
save(noi, "0")
for i in range(1, 6):
onoi = noi
noi = mdp(noi, 64 // i)
save(noi, str(i))
import random
def createColoredGrid(width, height, padding, nboxes, padding_color, colors):
box_len = width // nboxes
columns = (width // box_len) + 1
row_colors = [colors[x % len(colors)] for x in range(columns)]
pixels = []
for y in range(height):
if (y % box_len == 0):
random.shuffle(row_colors)
row = [padding_color] * width
y_offset = y % box_len
if (y_offset >= padding and y_offset <= box_len):
for x in range(len(row)):
x_offset = x % box_len
if (x_offset < padding or x_offset > box_len):
continue
row[x] = row_colors[x // box_len]
pixels.append(row)
return pixels
def toPPM(pixels):
height = len(pixels)
width = len(pixels[0])
buf = []
buf.append("P3")
buf.append("%i %i\n%i" % (width, height, 255))
for row in pixels:
for pixel in row:
r = (pixel >> 16) & 0xFF
g = (pixel >> 8) & 0xFF
b = pixel & 0xFF
buf.append("%i %i %i" % (r, g, b))
return "\n".join(buf)
if (__name__ == "__main__"):
colors = [0xffa70f, 0xff4948, 0x700090, 0x878787, 0x8f3900]
filler = 0x000000
pixels = createColoredGrid(1600, 1000, 5, 50, filler, colors)
ppm = toPPM(pixels)
with open("output.ppm", "w") as fh:
fh.write(ppm)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.