-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathinit.lua
357 lines (300 loc) · 10.5 KB
/
init.lua
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
local path = "heightmap.dat"
local conf_path = "heightmap.dat.conf"
local worldpath = minetest.get_worldpath()
local modpath = minetest.get_modpath(minetest.get_current_modname())
file = io.open(worldpath .. "/" .. path)
local conf = Settings(worldpath .. "/" .. conf_path)
-- Configuration
local scale_x = tonumber(conf:get("scale_x")) or 1
local scale_y = tonumber(conf:get("scale_y")) or conf:get("scale") or 1
local scale_z = tonumber(conf:get("scale_z")) or 1
local offset_x = tonumber(conf:get("offset_x")) or 0
local offset_y = tonumber(conf:get("offset_y")) or 0
local offset_z = tonumber(conf:get("offset_z")) or 0
local function get_bool(name)
local v = conf:get_bool(name)
if v == nil then
return true -- Enable by default, disable only if explicitly set to false
end
return false
end
local enable_rivers = get_bool("rivers")
local enable_landcover = get_bool("landcover")
local enable_trees = get_bool("trees")
local enable_plants = get_bool("plants")
local remove_delay = 10 -- Number of mapgen calls until a chunk is unloaded
local function parse(str, signed) -- little endian
local bytes = {str:byte(1, -1)}
local n = 0
local byte_val = 1
for _, byte in ipairs(bytes) do
n = n + (byte * byte_val)
byte_val = byte_val * 256
end
if signed and n >= byte_val / 2 then
return n - byte_val
end
return n
end
if file:read(5) ~= "GEOMG" then
print('[geo_mapgen] WARNING: file may not be in the appropriate format. Signature "GEOMG" not recognized.')
end
local version = parse(file:read(1))
-- Geometry stuff
local frag = parse(file:read(2))
local X = parse(file:read(2))
local Z = parse(file:read(2))
local chunks_x = math.ceil(X / frag) -- Number of chunks along X axis
local chunks_z = math.ceil(Z / frag) -- Number of chunks along Z axis
local xmin = math.ceil(offset_x)
local xmax = math.floor(X/scale_x+offset_x)
local zmin = math.ceil(-Z/scale_z+offset_z)
local zmax = math.floor(offset_z)
local last_chunk_length = (X-1) % frag + 1 -- Needed for incrementing index because last chunk may be truncated in length and therefore need an unusual increment
local function displaytime(time)
return math.floor(time * 1000000 + 0.5) / 1000 .. " ms"
end
-- Metatables
local function load_chunk(layer, n)
print("[geo_mapgen] Loading chunk " .. n)
local t1 = os.clock()
local index = layer.index
local address_min = index[n-1] -- inclusive
local address_max = index[n] -- exclusive
local count = address_max - address_min
file:seek("set", layer.offset + address_min)
local data_raw = minetest.decompress(file:read(count))
layer[n] = data_raw -- Put raw data in table
layer.delay[n] = remove_delay -- Set delay for this chunk
local t2 = os.clock()
print("[geo_mapgen] Loaded chunk " .. n .. " in " .. displaytime(t2-t1))
return data_raw
end
local mt = {__index = load_chunk} -- Metatable that will allow to load chunks on request
local delays = {} -- Will be a list of delay tables. A delay table is a table that contains the delay before unload of every loaded chunk.
local heightmap = nil
local rivermap = nil
local rivers = false
local biomemap = nil
local biomes = false
local biome_list = {}
-- Layers
local layers = {}
local layer_count = parse(file:read(1))
for l=1, layer_count do
local datatype = parse(file:read(1)) -- Type of data: 0 = heightmap, 1 = rivermap
local itemsize_raw = parse(file:read(1))
local signed = false
local itemsize = itemsize_raw
if itemsize >= 16 then
signed = true
itemsize = itemsize_raw - 16
end
local index_length = parse(file:read(4))
local meta = ""
if version >= 1 then
local meta_length = parse(file:read(2))
meta = file:read(meta_length)
end
local index_raw = minetest.decompress(file:read(index_length))
local index = {[0] = 0} -- Variable is called index instead of table to avoid name conflicts. Will contain a list of the ending position for every chunk, begin at chunk 1, so (unexisting) chunk 0 would end at pos 0. This makes simpler the calculation of chunk size that is index[i] - index[i-1] even for i=1.
for i=1, #index_raw / 4 do
index[i] = parse(index_raw:sub(i*4-3, i*4))
end
local delay = {} -- Delay table, will contain the number of mapgen calls before unloading, for every loaded chunk
delays[l] = delay
local layer = {
delay = delay,
offset = file:seek(), -- Position of first data
itemsize = itemsize,
signed = signed,
index = index,
meta = meta,
}
delay.data = layer -- Reference layer in delay table
setmetatable(layer, mt)
if datatype == 0 then -- Code for heightmap
heightmap = layer
elseif datatype == 1 then
print("Rivermap enabled!")
rivermap = layer
rivers = enable_rivers
elseif datatype == 2 then
print("Biomemap enabled!")
biomemap = layer
biomes = enable_landcover
local biomes_by_name = dofile(modpath .. "/landcover.lua") -- Load biome descriptions
local biomenames = meta:split(',', true)
for i, name in ipairs(biomenames) do
biome_list[i] = biomes_by_name[name]
end
end
local data_size = index[#index]
file:seek("cur", data_size) -- Skip data and go to the position of the next layer
end
local function choose_deco(decos)
local r = math.random()
for _, deco_params in ipairs(decos) do
local prob = deco_params.prob
if r < prob then
local deco = deco_params.deco
return deco.list[math.random(#deco.list)], deco.is_schem
else
r = r - prob
end
end
end
local data = {}
minetest.register_on_mapgen_init(function(mgparams)
minetest.set_mapgen_params({mgname="singlenode", flags="nolight"})
end)
-- Timing stuff
local mapgens = 0
local time_sum = 0
local time_sum2 = 0
-- Decode the value of a chunk for a given pixel
local function value(layer, nchunk, n)
local itemsize = layer.itemsize
return parse(layer[nchunk]:sub((n-1)*itemsize + 1, n*itemsize), layer.signed)
end
minetest.register_on_generated(function(minp, maxp, seed)
print("[geo_mapgen] Generating from " .. minetest.pos_to_string(minp) .. " to " .. minetest.pos_to_string(maxp))
local t0 = os.clock()
local c_stone = minetest.get_content_id("default:stone")
local c_dirt = minetest.get_content_id("default:dirt")
local c_lawn = minetest.get_content_id("default:dirt_with_grass")
local c_water = minetest.get_content_id("default:water_source")
local c_rwater = minetest.get_content_id("default:river_water_source")
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
vm:get_data(data)
local a = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local ystride = a.ystride
local schems_to_generate = {}
for x = math.max(xmin, minp.x), math.min(xmax, maxp.x) do
for z = math.max(zmin, minp.z), math.min(zmax, maxp.z) do
local ivm = a:index(x, minp.y, z)
local xmap = math.floor((x-offset_x) * scale_x)
local zmap = math.floor((z-offset_z) * scale_z)
local xchunk = math.floor(xmap / frag)
local zchunk = math.floor(-zmap / frag)
local nchunk = xchunk + zchunk * chunks_x + 1
local increment = frag
if xchunk + 1 == chunks_x then -- Last chunk of the line: may be truncated, that would change the line increment.
increment = last_chunk_length
end
local xpx = xmap % frag
local zpx = -zmap % frag
local npx = xpx + zpx * increment + 1 -- Increment is used here
local h = math.floor(value(heightmap, nchunk, npx) / scale_y + offset_y)
if minp.y <= math.max(h,offset_y) then
local river_here = false
if rivers then
river_here = value(rivermap, nchunk, npx) > 0
end
local stone, filler, top = c_stone, c_dirt, c_lawn
local nfiller, ntop = 3, 1
local node_deco
if biomes and h >= offset_y then
local nbiome = value(biomemap, nchunk, npx)
local biome = biome_list[nbiome]
if biome then
stone = biome.stone
filler = biome.filler
top = biome.top
nfiller = biome.filler_depth
ntop = biome.top_depth
if enable_plants and maxp.y >= h and not river_here then -- Generate decoration
local deco, is_schem = choose_deco(biome.decos)
if deco then
if is_schem then
if enable_trees then
table.insert(schems_to_generate, {pos={x=x-2,y=h+1,z=z-2}, schem=deco}) -- Schem size is not known. Assuming that most of schematics have a size of 5, hardcode offset to 2. TODO: Change that when schematic flags will be available on minetest.place_schematic_on_vmanip
end
else
node_deco = deco
end
end
end
end
end
if h < offset_y then
top = filler
end
local stone_min = minp.y
local stone_max = math.min(h-nfiller, maxp.y)
local filler_min = math.max(stone_max+1, minp.y)
local filler_max = math.min(h-ntop, maxp.y)
local top_min = math.max(filler_max+1, minp.y)
local top_max = math.min(h, maxp.y)
if river_here then
top_max = math.min(h-1, maxp.y)
end
if stone_min <= stone_max then
for y = stone_min, stone_max do
data[ivm] = stone
ivm = ivm + ystride
end
end
if filler_min <= filler_max then
for y = filler_min, filler_max do
data[ivm] = filler
ivm = ivm + ystride
end
end
if top_min <= top_max then
for y = top_min, top_max do
data[ivm] = top
ivm = ivm + ystride
end
end
if river_here then
data[ivm] = c_rwater
ivm = ivm + ystride
elseif node_deco and h >= offset_y then
data[ivm] = node_deco
end
if h < offset_y then
for y = math.max(h+1, minp.y), math.min(offset_y, maxp.y) do
data[ivm] = c_water
ivm = ivm + ystride
end
end
end
end
end
vm:set_data(data)
for _, params in ipairs(schems_to_generate) do
minetest.place_schematic_on_vmanip(vm, params.pos, params.schem, "random", nil, false) -- Place schematics
end
vm:set_lighting({day = 0, night = 0})
vm:calc_lighting()
vm:update_liquids()
vm:write_to_map()
-- Decrease delay, remove chunks from cache when time is over
for _, layer_delays in ipairs(delays) do
for n, delay in pairs(layer_delays) do
if n ~= "data" then -- avoid the "data" field!
if delay <= 1 then
layer_delays[n] = nil
layer_delays.data[n] = nil -- layer_delays.data is the layer itself
print("[geo_mapgen] Uncaching chunk " .. n)
else
layer_delays[n] = delay - 1
end
end
end
end
local t3 = os.clock()
local time = t3 - t0
print("[geo_mapgen] Mapgen finished in " .. displaytime(time))
mapgens = mapgens + 1
time_sum = time_sum + time
time_sum2 = time_sum2 + time ^ 2
end)
minetest.register_on_shutdown(function()
print("[geo_mapgen] Mapgen calls: " .. mapgens)
local average = time_sum / mapgens
print("[geo_mapgen] Average time: " .. displaytime(average))
local stdev = math.sqrt(time_sum2 / mapgens - average ^ 2)
print("[geo_mapgen] Standard dev: " .. displaytime(stdev))
end)