worldgen 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/worldgen/diamondsquare.c +1 -3
- data/ext/worldgen/heightmap.c +29 -0
- data/generate.rb +10 -4
- data/lib/worldgen.rb +1 -0
- data/lib/worldgen/platemap.rb +230 -0
- data/lib/worldgen/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75fa665add8ad72d23153e40d262bdd86a553421
|
4
|
+
data.tar.gz: 4920045ba83ad3d26d0f2c424555ccd91be46d02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e95a80c4f3043220615ab1a5cae52e3ab0276460d236deb23ddc52bc0bfece292349c14682089a1148a219319c0c5ca7573957f14b23fb0a97ca5ac1a6b955e5
|
7
|
+
data.tar.gz: 0f6506cf096612852a6bd47d4e0e5ec8f797634bbc5a36ccac51eb116b33627893a923111535fa32fc9e485ebb444f276c6a40dc8375a39770e1b6c7df9a93aa
|
@@ -38,9 +38,7 @@ VALUE diamond_square(int argc, VALUE *argv, VALUE self) {
|
|
38
38
|
|
39
39
|
roughness = vroughness == Qnil ? DEFAULT_ROUGHNESS : NUM2DBL(vroughness);
|
40
40
|
|
41
|
-
|
42
|
-
// if we don't
|
43
|
-
heights = (double *)malloc(sizeof(double) * num_points(size));
|
41
|
+
heights = (heightmap_points)malloc(sizeof(double) * num_points(size));
|
44
42
|
|
45
43
|
memset(heights, 0.0, sizeof(double) * num_points(size));
|
46
44
|
|
data/ext/worldgen/heightmap.c
CHANGED
@@ -91,6 +91,33 @@ VALUE each_height(VALUE self) {
|
|
91
91
|
return self;
|
92
92
|
}
|
93
93
|
|
94
|
+
/**
|
95
|
+
* Get the value of our heightmap at a specified point.
|
96
|
+
*/
|
97
|
+
VALUE get_at(VALUE self, VALUE vx, VALUE vy) {
|
98
|
+
int x = FIX2INT(vx);
|
99
|
+
int y = FIX2INT(vy);
|
100
|
+
heightmap map = get_heights(self);
|
101
|
+
int size = map.size;
|
102
|
+
|
103
|
+
return ARR(map.heights, x, y);
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Set the value of our heightmap at a specified point.
|
108
|
+
*/
|
109
|
+
VALUE set_at(VALUE self, VALUE vx, VALUE vy, VALUE vheight) {
|
110
|
+
int x = FIX2INT(vx);
|
111
|
+
int y = FIX2INT(vy);
|
112
|
+
double height = NUM2DBL(vheight);
|
113
|
+
heightmap map = get_heights(self);
|
114
|
+
int size = map.size;
|
115
|
+
|
116
|
+
ARR(map.heights, x, y) = height;
|
117
|
+
|
118
|
+
return vheight;
|
119
|
+
}
|
120
|
+
|
94
121
|
void load_heightmap() {
|
95
122
|
VALUE mod, height_map;
|
96
123
|
|
@@ -101,6 +128,8 @@ void load_heightmap() {
|
|
101
128
|
rb_define_private_method(height_map, "initialize_native", initialize_native, 1);
|
102
129
|
rb_define_method(height_map, "num_points", num_points_wrapped, 0);
|
103
130
|
rb_define_method(height_map, "each_height", each_height, 0);
|
131
|
+
rb_define_method(height_map, "[]=", get_at, 2);
|
132
|
+
rb_define_method(height_map, "[]=", set_at, 3);
|
104
133
|
|
105
134
|
HeightmapData = rb_define_class_under(height_map, "HeightmapData", rb_cObject);
|
106
135
|
}
|
data/generate.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require "worldgen"
|
3
3
|
|
4
|
-
options = {
|
4
|
+
options = {
|
5
|
+
verbose: true
|
6
|
+
}
|
5
7
|
|
6
8
|
OptionParser.new do |opts|
|
7
9
|
opts.on("--heightmap [FILE]", String, "Output a heightmap to FILE") do |file|
|
@@ -19,6 +21,10 @@ OptionParser.new do |opts|
|
|
19
21
|
opts.on("--num-plates [N]", Integer, "Generate N plates") do |n|
|
20
22
|
options[:num_plates] = n
|
21
23
|
end
|
24
|
+
|
25
|
+
opts.on("-q", "--quiet", "Disable verbose logging") do |n|
|
26
|
+
options[:verbose] = false
|
27
|
+
end
|
22
28
|
end.parse!
|
23
29
|
|
24
30
|
if not options[:size]
|
@@ -41,10 +47,10 @@ if options[:platemap]
|
|
41
47
|
end
|
42
48
|
|
43
49
|
platemap = Worldgen::PlateMap.new(options[:size])
|
44
|
-
platemap.generate_plates!
|
50
|
+
platemap.generate_plates!(options[:num_plates], options[:verbose])
|
45
51
|
puts "Converting to height map..."
|
46
|
-
heightmap = platemap.to_height_map
|
47
|
-
|
52
|
+
heightmap = platemap.to_height_map(0.5)
|
53
|
+
Worldgen::Render.heightmap heightmap, options[:platemap]
|
48
54
|
end
|
49
55
|
|
50
56
|
puts "Done."
|
data/lib/worldgen.rb
CHANGED
@@ -0,0 +1,230 @@
|
|
1
|
+
module Worldgen
|
2
|
+
# A map that generates plates within a square map.
|
3
|
+
class PlateMap
|
4
|
+
# A class representing a single plate.
|
5
|
+
# TODO: using Ruby for this is kinda slow, maybe port a good chunk of this
|
6
|
+
# stuff to C
|
7
|
+
class Plate
|
8
|
+
# Attributes:
|
9
|
+
# * seed - the initial point within the map for this plate
|
10
|
+
# * map - the map that this plate is in
|
11
|
+
# * id - the ID number of this plate (how this plate identifies itself)
|
12
|
+
# * type - the type of plate (continental vs. ocean)
|
13
|
+
attr_accessor :seed, :map, :id, :type
|
14
|
+
|
15
|
+
# Set the seed for this plate. Construct the frontier.
|
16
|
+
def seed= seed
|
17
|
+
@seed = seed
|
18
|
+
@frontier = empty_neighbours(@seed)
|
19
|
+
end
|
20
|
+
|
21
|
+
# See if this plate has a frontier or not
|
22
|
+
def has_frontier?
|
23
|
+
@frontier and @frontier.length > 0
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get the length of this plate's frontier
|
27
|
+
def frontier_length
|
28
|
+
@frontier ? @frontier.length : 0
|
29
|
+
end
|
30
|
+
|
31
|
+
# Absorb a single empty point along the frontier
|
32
|
+
def absorb_frontier!
|
33
|
+
# Shuffle the frontier so that we end up grabbing a random point
|
34
|
+
@frontier.shuffle!
|
35
|
+
value = nil
|
36
|
+
|
37
|
+
# It's possible another plate has absorbed part of our frontier since
|
38
|
+
# the last time we set our frontier, so shift until we find an empty
|
39
|
+
# spot
|
40
|
+
while value == nil and @frontier.length > 0
|
41
|
+
value = @frontier.shift
|
42
|
+
value = nil unless at(*value) < 0
|
43
|
+
end
|
44
|
+
|
45
|
+
if value
|
46
|
+
# move it into me
|
47
|
+
@map[value[0]][value[1]] = @id
|
48
|
+
|
49
|
+
# add new points onto my frontier
|
50
|
+
@frontier += empty_neighbours(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the empty neighbours around `point`
|
57
|
+
def empty_neighbours point
|
58
|
+
neighbours(point).select do |(x, y)|
|
59
|
+
at(x, y) < 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the neighbours of `point` - directly adjacent only, no diagonals
|
64
|
+
def neighbours point
|
65
|
+
[
|
66
|
+
[-1, 0],
|
67
|
+
[1, 0],
|
68
|
+
[0, 1],
|
69
|
+
[0, -1]
|
70
|
+
].map do |(dx, dy)|
|
71
|
+
[
|
72
|
+
(point[0] + dx + @map.length) % @map.length,
|
73
|
+
(point[1] + dy + @map.length) % @map.length
|
74
|
+
]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
# Get the point at x, y
|
80
|
+
def at x, y
|
81
|
+
@map[x][y]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :size
|
86
|
+
|
87
|
+
def initialize size
|
88
|
+
@size = size
|
89
|
+
@plate_ids = nil
|
90
|
+
@plates = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get the number of points in the map.
|
94
|
+
def num_points
|
95
|
+
@size * @size
|
96
|
+
end
|
97
|
+
|
98
|
+
# Convert to a height map - very simple, just give the continents one height
|
99
|
+
# and the oceans another height. This is controlled by a single parameter,
|
100
|
+
# (({sea_gap})), which will be the difference between the continents height and
|
101
|
+
# oceans height.
|
102
|
+
def to_height_map sea_gap
|
103
|
+
raise "Sea gap should be between 0 and 1" if sea_gap < 0 or sea_gap > 1
|
104
|
+
|
105
|
+
plate_heights = @plates.map do |plate|
|
106
|
+
if plate.type == :continent
|
107
|
+
0.5 + sea_gap / 2
|
108
|
+
else
|
109
|
+
0.5 - sea_gap / 2
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
map = HeightMap.new @size
|
114
|
+
|
115
|
+
each_plate_point do |x, y, id|
|
116
|
+
map[x, y] = plate_heights[id]
|
117
|
+
end
|
118
|
+
|
119
|
+
map
|
120
|
+
end
|
121
|
+
|
122
|
+
# Iterate across the entire map.
|
123
|
+
#
|
124
|
+
# Usage:
|
125
|
+
#
|
126
|
+
# each_plate_point do |x, y, plate_id|
|
127
|
+
# # do something with this information
|
128
|
+
# end
|
129
|
+
def each_plate_point
|
130
|
+
(0...@size).each do |x|
|
131
|
+
(0...@size).each do |y|
|
132
|
+
yield x, y, @plate_ids[x][y]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Generate plates within this map.
|
138
|
+
#
|
139
|
+
# Arguments:
|
140
|
+
# * num_plates - the number of plates to generate
|
141
|
+
# * verbose (default: true) - output logging while we're generating
|
142
|
+
def generate_plates! num_plates, verbose=true
|
143
|
+
@plate_ids = Array.new(@size) { Array.new(@size) { -1 }}
|
144
|
+
|
145
|
+
# Initialize plates in random spots
|
146
|
+
@plates = (0...num_plates).map do |plate_num|
|
147
|
+
# Find an unoccupied point
|
148
|
+
point = nil
|
149
|
+
while point == nil
|
150
|
+
x, y = rand(@size), rand(@size)
|
151
|
+
|
152
|
+
point = [x, y] if @plate_ids[x][y] < 0
|
153
|
+
end
|
154
|
+
|
155
|
+
x, y = point
|
156
|
+
@plate_ids[x][y] = plate_num
|
157
|
+
|
158
|
+
Plate.new.tap do |plate|
|
159
|
+
plate.map = @plate_ids
|
160
|
+
plate.id = plate_num
|
161
|
+
plate.type = rand < 0.5 ? :continent : :ocean
|
162
|
+
plate.seed = point
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
num_points = self.num_points - num_plates
|
167
|
+
valid_plates = @plates.select(&:has_frontier?)
|
168
|
+
|
169
|
+
i = 0
|
170
|
+
while valid_plates.length > 0
|
171
|
+
if verbose and i % (num_points / 100) == 0
|
172
|
+
puts "#{i}/#{num_points} #{(i.fdiv(num_points) * 100).round}%"
|
173
|
+
end
|
174
|
+
|
175
|
+
# Find a plate with neighbours
|
176
|
+
loop do
|
177
|
+
idx = choose_plate valid_plates
|
178
|
+
plate = valid_plates[idx]
|
179
|
+
|
180
|
+
# absorb a point from the frontier
|
181
|
+
value = plate.absorb_frontier!
|
182
|
+
|
183
|
+
if not value
|
184
|
+
valid_plates.delete_at idx
|
185
|
+
break if valid_plates.length == 0
|
186
|
+
else
|
187
|
+
break
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
i += 1
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
# Choose a plate randomly from a list of plates
|
198
|
+
def choose_plate plates
|
199
|
+
# Weighted choice based on frontier length - if we do it perfectly uniform
|
200
|
+
# then we end up with plates that "compress" into small remaining places
|
201
|
+
# which results in "snaky" plates and weird convergence points between
|
202
|
+
# many plates
|
203
|
+
|
204
|
+
# TODO: could probably pull weighted randoms into a different module
|
205
|
+
total = plates.map(&:frontier_length).inject(&:+)
|
206
|
+
|
207
|
+
# TODO: using a uniform random generator here gives plates that are all
|
208
|
+
# roughly the same size - kinda boring, maybe try using a non-uniform
|
209
|
+
# distribution here
|
210
|
+
point = rand(total)
|
211
|
+
|
212
|
+
idx = 0
|
213
|
+
begin
|
214
|
+
while point > plates[idx].frontier_length
|
215
|
+
point -= plates[idx].frontier_length
|
216
|
+
idx += 1
|
217
|
+
end
|
218
|
+
rescue
|
219
|
+
# TODO: fix this - once in a while we get an out of bounds problem here
|
220
|
+
puts $!
|
221
|
+
puts $!.backtrace.join("\n")
|
222
|
+
puts "Point: #{point}"
|
223
|
+
puts "Idx: #{idx}"
|
224
|
+
puts "Total: #{total}"
|
225
|
+
end
|
226
|
+
|
227
|
+
idx
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
data/lib/worldgen/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: worldgen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Britton
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- generate.rb
|
89
89
|
- lib/worldgen.rb
|
90
90
|
- lib/worldgen/heightmap.rb
|
91
|
+
- lib/worldgen/platemap.rb
|
91
92
|
- lib/worldgen/render.rb
|
92
93
|
- lib/worldgen/version.rb
|
93
94
|
- worldgen.gemspec
|