forked from axcore/australia
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvoxel.lua
executable file
·217 lines (178 loc) · 7.24 KB
/
voxel.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
-- mods/australia/voxel.lua
-- This is only used to handle cases the decoration manager can't, such as
-- more ore in specific biomes.
-- Define perlin noises used in this mapgen by default
aus.noises = {}
-- Noise 22 : Cave blend 2D
aus.noises[22] = {offset = 0.0, scale = 0.1, spread = {x = 8, y = 8, z = 8}, seed = 4023, octaves = 2, persist = 1.0, lacunarity = 2.0}
-- Noise 23 : Cave noise 2D
aus.noises[23] = {offset = 0.0, scale = 1.0, spread = {x = 400, y = 400, z = 400}, seed = 903, octaves = 3, persist = 0.5, lacunarity = 2.0}
-- function to get noisemaps
function aus.noisemap(i, minp, chulens)
local obj = minetest.get_perlin_map(aus.noises[i], chulens)
if minp.z then
return obj:get3dMap_flat(minp)
else
return obj:get2dMap_flat(minp)
end
end
-- useful function to convert a 3D pos to 2D
function pos2d(pos)
if type(pos) == "number" then
return {x = pos, y = pos}
elseif pos.z then
return {x = pos.x, y = pos.z}
else
return {x = pos.x, y = pos.y}
end
end
-- Define content IDs
-- A content ID is a number that represents a node in the core of Minetest.
-- Every nodename has its ID.
-- The VoxelManipulator uses content IDs instead of nodenames.
local node = {}
local nodes = {
-- Ground nodes
{"stone", "default:stone"},
{"dirt", "default:dirt"},
{"sand", "default:sand"},
-- Liquids
{"river_water_source", "default:river_water_source"},
{"muddy_river_water_source", "australia:muddy_river_water_source"},
{"water_source", "default:water_source"},
-- Air and Ignore
{"air", "air"},
{"ignore", "ignore"},
-- Resources
{"coalblock", "default:coalblock"},
{"copper", "default:stone_with_copper"},
{"diamond", "default:stone_with_diamond"},
{"gold", "default:stone_with_gold"},
{"iron", "default:stone_with_iron"},
}
for _, i in pairs(nodes) do
node[i[1]] = minetest.get_content_id(i[2])
end
-- Create a table of biome ids, so I can use the biomemap.
if not aus.biome_ids then
aus.biome_ids = {}
for name, desc in pairs(minetest.registered_biomes) do
local i = minetest.get_biome_id(desc.name)
aus.biome_ids[i] = desc.name
end
end
-- the mapgen function
function aus.generate(minp, maxp, seed)
-- minp and maxp strings, used by logs
local minps, maxps = minetest.pos_to_string(minp), minetest.pos_to_string(maxp)
-- The VoxelManipulator, a complicated but speedy method to set many nodes at the same time
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local heightmap = minetest.get_mapgen_object("heightmap")
local water_level = 1
local data = vm:get_data() -- data is the original array of content IDs (solely or mostly air)
-- Be careful: emin ≠ minp and emax ≠ maxp !
-- The data array is not limited by minp and maxp. It exceeds it by 16 nodes in the 6 directions.
-- The real limits of data array are emin and emax.
-- The VoxelArea is used to convert a position into an index for the array.
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local ystride = area.ystride -- Tip : the ystride of a VoxelArea is the number to add to the array index to get the index of the position above. It's faster because it avoids to completely recalculate the index.
local zstride = area.zstride
local chulens = vector.add(vector.subtract(maxp, minp), 1) -- Size of the generated area, used by noisemaps
local minp2d = pos2d(minp)
-- The biomemap is a table of biome index numbers for each horizontal
-- location. It's created in the mapgen, and is right most of the time.
-- It's off in about 1% of cases, for various reasons.
-- Bear in mind that biomes can change from one voxel to the next.
local biomemap = minetest.get_mapgen_object("biomemap")
-- Calculate the noise values
local n22 = aus.noisemap(22, minp2d, chulens)
local n23 = aus.noisemap(23, minp2d, chulens)
local node_match_cache = {}
-- the mapgen algorithm
local index_2d = 0
local write = false
local index_3d, air_count, ground
local index_3d_below, index_3d_above, surround
local biome, sr
local stone_type, stone_depth, n23_val
for z = minp.z, maxp.z do -- for each YZ plane
for x = minp.x, maxp.x do -- for each vertical line in this plane
index_2d = index_2d + 1
local index_3d = area:index(x, maxp.y, z) -- index of the data array, matching the position {x, y, z}
local air_count = 0
local ground = math.max(heightmap[index_2d], 0) - 5
for y = maxp.y, minp.y, -1 do -- for each node in vertical line
local index_3d_below = index_3d - ystride
local index_3d_above = index_3d + ystride
-- Determine if a plant/dirt block can be placed without showing.
-- Avoid the edges of the chunk, just to make things easier.
if y < maxp.y and x > minp.x and x < maxp.x and z > minp.z and z < maxp.z and (data[index_3d] == node["sand"] or data[index_3d] == node["dirt"]) then
if data[index_3d_above] == node["river_water_source"] or data[index_3d_above] == node["muddy_river_water_source"] or data[index_3d_above] == node["water_source"] then
-- Check to make sure that a plant root is fully surrounded.
-- This is due to the kludgy way you have to make water plants
-- in minetest, to avoid bubbles.
for x1 = -1,1,2 do
local n = data[index_3d+x1]
if n == node["river_water_source"] or n == node["muddy_river_water_source"] or n == node["water_source"] or n == node["air"] then
surround = false
end
end
for z1 = -zstride,zstride,2*zstride do
local n = data[index_3d+z1]
if n == node["river_water_source"] or n == node["muddy_river_water_source"] or n == node["water_source"] or n == node["air"] then
surround = false
end
end
end
-- Extra resources in ground per biome.
if y < ground and (data[index_3d] == node["air"] or data[index_3d] == node["river_water_source"] or data[index_3d] == node["muddy_river_water_source"] or data[index_3d] == node["water_source"]) then
local biome = aus.biome_ids[biomemap[index_2d]]
local stone_type = node["stone"]
local stone_depth = 1
local n23_val = n23[index_2d] + n22[index_2d]
-- Change stone per biome.
if data[index_3d_below] == node["stone"] then
data[index_3d_below] = stone_type
if stone_depth == 2 then
data[index_3d_below - ystride] = stone_type
end
write = true
end
if data[index_3d_above] == node["stone"] then
data[index_3d_above] = stone_type
if stone_depth == 2 then
data[index_3d_above + ystride] = stone_type
end
write = true
end
if data[index_3d] == node["air"] then
air_count = air_count + 1
end
end
if data[index_3d] ~= node["air"] then
air_count = 0
end
index_3d = index_3d_below
end
end
end
-- execute voxelmanip boring stuff to write to the map...
if write then
vm:set_data(data)
end
if write then
-- This seems to be necessary to avoid lighting problems.
vm:calc_lighting()
end
if write then
vm:write_to_map()
end
-- Deal with memory issues. This, of course, is supposed to be automatic.
local mem = math.floor(collectgarbage("count")/1024)
if mem > 500 then
minetest.log("info", "MOD: Australia is manually collecting garbage as memory use has exceeded 500K.")
collectgarbage("collect")
end
end
-- Call the mapgen function aus.generate on mapgen.
minetest.register_on_generated(aus.generate)