-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.rb
158 lines (142 loc) · 5.52 KB
/
main.rb
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
# 2D Array.
class Matrix
attr_accessor :columns, :width, :height
def initialize(width, height, &default_contents)
@columns = Array.new(width) { |x| Array.new(height) { |y| default_contents.call(x, y) } }
@width = width
@height = height
end
# Some custom iterators
def each_row
@columns[0].each_with_index do |entry, row_number|
current_row = []
@columns.each do |column|
current_row << column[row_number]
end
yield current_row
end
end
def each_row_with_index
@columns[0].each_with_index do |entry, row_number|
current_row = []
@columns.each do |column|
current_row << column[row_number]
end
yield current_row, row_number
end
end
def each_column
@columns.each do |column|
yield column
end
end
def each_column_with_index
@columns.each_with_index do |column, column_number|
yield column, column_number
end
end
# Print the matrix
def display
each_row do |row|
row.each do |entry|
if entry.is_a?(Float)
representation = sprintf('%.2f', entry)
else
representation = entry.to_s
end
print representation
number_of_padding_spaces = 6 - representation.size
number_of_padding_spaces.times do
print ' '
end
end
print "\n"
end
end
end
# Put enough random numbers in a matrix to fill the target matrix or, if the period is at least the matrix size, enough to make it not wrap around
def random_matrix_for_octave(octave_number, parameters)
matrix_width = [2 ** octave_number + 1, parameters[:width]].max
matrix_height = [2 ** octave_number + 1, parameters[:height]].max
return Matrix.new(matrix_width, matrix_height + 1) { |x, y| rand }
end
# This will be the main function
def matrix_with_noise(parameters)
if parameters[:method] == 'perlin'
world = perlin(parameters)
elsif parameters[:method] == 'worley'
elsif parameters[:method] == 'diamond-square'
end
return world
end
# Returns a matrix filled with Perlin noise starting from a random matrix with values between 0.0 and 1.0.
# Based on http://devmag.org.za/2009/04/25/perlin-noise/
def perlin(parameters)
# First create and store each octave. If the :octave_amplitudes parameter is set, the program uses manually specified amplitudes; otherwise, it uses the :octave_count and :persistence parameters.
octaves = {}
if parameters.has_key?(:octave_amplitudes)
parameters[:octave_amplitudes].each_key do |octave_number|
octaves[octave_number] = perlin_octave(octave_number, random_matrix_for_octave(octave_number, parameters))
end
else
parameters[:octave_count].times do |octave_number|
octaves[octave_number] = perlin_octave(octave_number, random_matrix_for_octave(octave_number, parameters))
end
end
# Mix the octaves together based on an amplitude that decreases exponentially according to a persistence parameter.
out_world = Matrix.new(parameters[:width], parameters[:height]) { |x, y| 0 }
current_amplitude = 1.0
total_amplitude = 0.0
# Set octave amplitude either by persistence or based on explicitly specified amplitudes.
octaves.keys.reverse_each do |octave_number|
if parameters.has_key?(:octave_amplitudes)
current_amplitude = parameters[:octave_amplitudes][octave_number]
else
current_amplitude *= parameters[:persistence]
end
total_amplitude += current_amplitude
out_world.each_column_with_index do |column, column_number|
column.each_with_index do |entry, row_number|
out_world.columns[column_number][row_number] += current_amplitude * octaves[octave_number].columns[column_number][row_number]
end
end
end
# Normalize the values to end up between 0 and 1.
out_world.each_column_with_index do |column, column_number|
column.each_with_index do |entry, row_number|
out_world.columns[column_number][row_number] /= total_amplitude
end
end
return out_world
end
# Fills a matrix with smoothed-out noise depending on octave number.
# Based on http://devmag.org.za/2009/04/25/perlin-noise/
def perlin_octave(octave_number, world)
octave = Matrix.new(world.width, world.height) { |x, y| 0 }
period = 2 ** octave_number
frequency = 1.0 / period
world.each_column_with_index do |column, column_number|
# Set left and right indices to interpolate between.
leftmost = (column_number / period) * period
rightmost = (leftmost + period) % world.width
horizontal_blend = (column_number - leftmost) * frequency
column.each_with_index do |entry, row_number|
# Set top and bottom indices to interpolate between.
topmost = (row_number / period) * period
bottommost = (topmost + period) % world.height
vertical_blend = (row_number - topmost) * frequency
top_value = interpolate(world.columns[leftmost][topmost], world.columns[rightmost][topmost], horizontal_blend)
bottom_value = interpolate(world.columns[leftmost][bottommost], world.columns[rightmost][bottommost], horizontal_blend)
octave.columns[column_number][row_number] = interpolate(top_value, bottom_value, vertical_blend)
end
end
return octave
end
# Linear interpolation between two values. "weight" is the weight of the second value.
def interpolate(a, b, weight)
return a * (1 - weight) + b * weight
end
def test_me_with(parameters)
matrix_with_noise(parameters).display
end
test_me_with({width: 16, height: 32, method: 'perlin', octave_count: 4, persistence: 0.9, octave_amplitudes: { 5 => 0.5, 4 => 0.5, 3 => 0.5, 2 => 0.1, 1 => 0.1, 0 => 0.3 } })