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 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