worldgen 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de4a34b0248c80788bc6ce2f4a12403b49e8df05
4
- data.tar.gz: d8210298cd91385beab0cb5366dcc19bfba9eefd
3
+ metadata.gz: 75fa665add8ad72d23153e40d262bdd86a553421
4
+ data.tar.gz: 4920045ba83ad3d26d0f2c424555ccd91be46d02
5
5
  SHA512:
6
- metadata.gz: 2829eab4c21503c6243774487c0121dd84fc41300103759116d97c2eeb378ed0a0b5cce0a2f16061d70e24b81bd8ec226375abda3e50827828e3d5316ab8b26f
7
- data.tar.gz: 58b82dcbc654c466ced978d629881bcba395d4577becff34b40017b912bff9a51462668e426033c36ec7106618be51c2e7b1d066fc24054dd55270acf1822691
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
- // need to allocate on the heap since Ruby will throw stack size problems
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
 
@@ -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
  }
@@ -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! options[:num_plates]
50
+ platemap.generate_plates!(options[:num_plates], options[:verbose])
45
51
  puts "Converting to height map..."
46
- heightmap = platemap.to_height_map
47
- render_heightmap heightmap, options[:platemap]
52
+ heightmap = platemap.to_height_map(0.5)
53
+ Worldgen::Render.heightmap heightmap, options[:platemap]
48
54
  end
49
55
 
50
56
  puts "Done."
@@ -1,5 +1,6 @@
1
1
  require "worldgen/version"
2
2
  require "worldgen/heightmap"
3
+ require "worldgen/platemap"
3
4
  require "worldgen/render"
4
5
  require_relative "worldgen.so"
5
6
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Worldgen
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
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.2
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