-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathLandscape_generator.py
174 lines (157 loc) · 6.92 KB
/
Landscape_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# coding: utf-8
# Landscape Generation with Midpoint displacement algorithm
# author : Juan Gallostra
# date : 10/12/2016
# version : 0.1.1
import os # path resolving and image saving
import random # midpoint displacement
from PIL import Image, ImageDraw # image creation and drawing
import bisect # working with the sorted list of points
from colourlovers import clapi # to vary colour palettes
import argparse # command line arguments
parser = argparse.ArgumentParser(description="2D Procedural Landscape generator")
parser.add_argument("-t", "--theme", type=str, help="theme for colour palette")
args = parser.parse_args()
# Iterative midpoint vertical displacement
def midpoint_displacement(
start, end, roughness, vertical_displacement=None, num_of_iterations=16
):
"""
Given a straight line segment specified by a starting point and an endpoint
in the form of [starting_point_x, starting_point_y] and [endpoint_x, endpoint_y],
a roughness value > 0, an initial vertical displacement and a number of
iterations > 0 applies the midpoint algorithm to the specified segment and
returns the obtained list of points in the form
points = [[x_0, y_0],[x_1, y_1],...,[x_n, y_n]]
"""
# Final number of points = (2^iterations)+1
if vertical_displacement is None:
# if no initial displacement is specified set displacement to:
# (y_start+y_end)/2
vertical_displacement = (start[1] + end[1]) / 2
# Data structure that stores the points is a list of lists where
# each sublist represents a point and holds its x and y coordinates:
# points=[[x_0, y_0],[x_1, y_1],...,[x_n, y_n]]
# | | |
# point 0 point 1 point n
# The points list is always kept sorted from smallest to biggest x-value
points = [start, end]
iteration = 1
while iteration <= num_of_iterations:
# Since the list of points will be dynamically updated with the new computed
# points after each midpoint displacement it is necessary to create a copy
# of the state at the beginning of the iteration so we can iterate over
# the original sequence.
# Tuple type is used for security reasons since they are immutable in Python.
points_tup = tuple(points)
for i in range(len(points_tup) - 1):
# Calculate x and y midpoint coordinates:
# [(x_i+x_(i+1))/2, (y_i+y_(i+1))/2]
midpoint = list(
map(lambda x: (points_tup[i][x] + points_tup[i + 1][x]) / 2, [0, 1])
)
# Displace midpoint y-coordinate
midpoint[1] += random.choice(
[-vertical_displacement, vertical_displacement]
)
# Insert the displaced midpoint in the current list of points
bisect.insort(points, midpoint)
# bisect allows to insert an element in a list so that its order
# is preserved.
# By default the maintained order is from smallest to biggest list first
# element which is what we want.
# Reduce displacement range
vertical_displacement *= 2 ** (-roughness)
# update number of iterations
iteration += 1
return points
def draw_layers(layers, width, height, colour_palette_keyword):
""" Compute the points that conform each of the layers in the Landscape """
colour_dict = None
# if a colour theme was specified
if colour_palette_keyword:
cl = clapi.ColourLovers()
palettes = cl.search_palettes(
request="top", keywords=colour_palette_keyword, numResults=15
)
palette = palettes[random.choice(range(len(palettes)))]
colour_dict = {
str(iter): palette.hex_to_rgb()[iter] for iter in range(len(palette.colors))
}
# Default colour palette
if colour_dict is None or len(colour_dict.keys()) < len(layers):
colour_dict = {
"0": (195, 157, 224),
"1": (158, 98, 204),
"2": (130, 79, 138),
"3": (68, 28, 99),
"4": (49, 7, 82),
"5": (23, 3, 38),
"6": (240, 203, 163),
}
else:
# len(colour_dict) should be at least: # of layers +1 (background colour)
if len(colour_dict) < len(layers) + 1:
raise ValueError("Num of colours should be bigger than the num of layers")
# Create image into which the terrain will be drawn
landscape = Image.new(
"RGBA", (width, height), colour_dict[str(len(colour_dict) - 1)]
)
landscape_draw = ImageDraw.Draw(landscape)
# Draw the sun
landscape_draw.ellipse((50, 25, 100, 75), fill=(255, 255, 255, 255))
# Sample the y values of all x in image
final_layers = []
for layer in layers:
sampled_layer = []
for i in range(len(layer) - 1):
sampled_layer += [layer[i]]
# If x difference is greater than 1
if layer[i + 1][0] - layer[i][0] > 1:
# Linearly sample the y values in the range x_[i+1]-x_[i]
# This is done by obtaining the equation of the straight
# line (in the form of y=m*x+n) that connects two consecutive
# points
m = float(layer[i + 1][1] - layer[i][1]) / (
layer[i + 1][0] - layer[i][0]
)
n = layer[i][1] - m * layer[i][0]
r = lambda x: m * x + n # straight line
for j in range(
int(layer[i][0] + 1), int(layer[i + 1][0])
): # for all missing x
sampled_layer += [[j, r(j)]] # Sample points
final_layers += [sampled_layer]
final_layers_enum = enumerate(final_layers)
for final_layer in final_layers_enum:
# traverse all x values in the layer
for x in range(len(final_layer[1]) - 1):
# for each x value draw a line from its y value to the bottom
landscape_draw.line(
(
final_layer[1][x][0],
height - final_layer[1][x][1],
final_layer[1][x][0],
height,
),
colour_dict[str(final_layer[0])],
)
return landscape
def main():
""" Entry point of the program """
width = 1000 # Terrain width
height = 500 # Terrain height
# Compute different layers of the landscape
layer_1 = midpoint_displacement([250, 0], [width, 200], 1.4, 20, 12)
layer_2 = midpoint_displacement([0, 180], [width, 80], 1.2, 30, 12)
layer_3 = midpoint_displacement([0, 270], [width, 190], 1, 120, 9)
layer_4 = midpoint_displacement([0, 350], [width, 320], 0.9, 250, 8)
colour_theme = None
if args.theme:
colour_theme = args.theme
landscape = draw_layers(
[layer_4, layer_3, layer_2, layer_1], width, height, colour_theme
)
landscape.save(os.getcwd() + "\\testing.png")
if __name__ == "__main__":
main()