-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmapgen.lua
814 lines (709 loc) · 34.7 KB
/
mapgen.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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
-- Mapgen 3.0
-- Friday September 21, 2018
-- Define perlin noises used in this mapgen by default
vmg.noises = {
-- Noise 1 : Base Ground Height 2D
{offset = -10, scale = 50, seed = 5202, spread = {x = 1024, y = 1024, z = 1024}, octaves = 6, persist = 0.4, lacunarity = 2},
-- Noise 2 : Valleys (River where around zero) 2D
{offset = 0, scale = 1, seed = -6050, spread = {x = 256, y = 256, z = 256}, octaves = 5, persist = 0.6, lacunarity = 2},
-- Noise 3 : Valleys Depth 2D
{offset = 5, scale = 4, seed = -1914, spread = {x = 512, y = 512, z = 512}, octaves = 1, persist = 1, lacunarity = 2},
-- Noise 4 : Valleys Profile (Higher values = Larger valleys) 2D
{offset = 0.6, scale = 0.5, seed = 777, spread = {x = 512, y = 512, z = 512}, octaves = 1, persist = 1, lacunarity = 2},
-- Noise 5 : Inter-valleys slopes 2D
{offset = 0.5, scale = 0.5, seed = 746, spread = {x = 128, y = 128, z = 128}, octaves = 1, persist = 1, lacunarity = 2},
-- Noise 6 : Inter-valleys filling 3D
{offset = 0, scale = 1, seed = 1993, spread = {x = 256, y = 512, z = 256}, octaves = 6, persist = 0.8, lacunarity = 2},
-- Noise 7 : Dirt thickness 2D
{offset = 4, scale = 1.75, seed = 1605, spread = {x = 256, y = 256, z = 256}, octaves = 3, persist = 0.5, lacunarity = 2},
-- Noise 8 : Caves I 3D
{offset = 0, scale = 1, seed = -4640, spread = {x = 32, y = 32, z = 32}, octaves = 4, persist = 0.5, lacunarity = 2},
-- Noise 9 : Caves II 3D
{offset = 0, scale = 1, seed = 8804, spread = {x = 32, y = 32, z = 32}, octaves = 4, persist = 0.5, lacunarity = 2},
-- Noise 10 : Caves III 3D
{offset = 0, scale = 1, seed = -4780, spread = {x = 32, y = 32, z = 32}, octaves = 4, persist = 0.5, lacunarity = 2},
-- Noise 11 : Caves IV and Lava I 3D
{offset = 0, scale = 1, seed = -9969, spread = {x = 32, y = 32, z = 32}, octaves = 4, persist = 0.5, lacunarity = 2},
-- Noise 12 : Lava II (Geologic heat) 3D
{offset = 0, scale = 1, seed = 3314, spread = {x = 64, y = 64, z = 64}, octaves = 4, persist = 0.5, lacunarity = 2},
-- Noise 13 : Clayey dirt noise 2D
{offset = 0, scale = 1, seed = 2835, spread = {x = 256, y = 256, z = 256}, octaves = 5, persist = 0.5, lacunarity = 4},
-- Noise 14 : Silty dirt noise 2D
{offset = 0, scale = 1, seed = 6674, spread = {x = 256, y = 256, z = 256}, octaves = 5, persist = 0.5, lacunarity = 4},
-- Noise 15 : Sandy dirt noise 2D
{offset = 0, scale = 1, seed = 6940, spread = {x = 256, y = 256, z = 256}, octaves = 5, persist = 0.5, lacunarity = 4},
-- Noise 16 : Beaches 2D
{offset = 2, scale = 8, seed = 2349, spread = {x = 256, y = 256, z = 256}, octaves = 3, persist = 0.5, lacunarity = 2},
-- Noise 17 : Temperature (not in maps) 3D
{offset = 2, scale = 1, seed = -1805, spread = {x = 768, y = 256, z = 768}, octaves = 4, persist = 0.5, lacunarity = 4},
-- Noise 18 : Humidity 2D
{offset = 0, scale = 1, seed = -5787, spread = {x = 243, y = 243, z = 243}, octaves = 4, persist = 0.5, lacunarity = 3},
-- Noise 19 : Simple Caves 1 3D
{offset = 0, scale = 1, seed = -8402, spread = {x = 64, y = 64, z = 64}, octaves = 3, persist = 0.5, lacunarity = 2},
-- Noise 20 : Simple Caves 2 3D
{offset = 0, scale = 1, seed = 3944, spread = {x = 64, y = 64, z = 64}, octaves = 3, persist = 0.5, lacunarity = 2},
}
-- function to get noisemaps
function vmg.noisemap(i, minp, chulens)
local obj = minetest.get_perlin_map(vmg.noises[i], chulens)
if minp.z then
return obj:get3dMap_flat(minp)
else
return obj:get2dMap_flat(minp)
end
end
-- If the noises are already defined in settings, use it instead of the noise parameters above.
for i, n in ipairs(vmg.noises) do
vmg.noises[i] = vmg.define("noise_" .. i, n)
end
-- List of functions to run at the end of the mapgen procedure, used especially by jungle tree roots
vmg.after_mapgen = {}
function vmg.register_after_mapgen(f, ...)
table.insert(vmg.after_mapgen, {f = f, ...})
end
function vmg.execute_after_mapgen()
for i, params in ipairs(vmg.after_mapgen) do
params.f(unpack(params))
end
vmg.after_mapgen = {}
end
-- Mapgen time stats
local mapgen_times = {
preparation = {},
noises = {},
collecting = {},
writing = {},
total = {},
}
-- Define parameters
local use_3d_rivers = vmg.define("3d_rivers", false)
local river_depth = vmg.define("river_depth", 3) + 1
local river_size = vmg.define("river_size", 5) / 100
local caves_size = vmg.define("caves_size", 7) / 100
local lava_depth = vmg.define("lava_depth", 2000)
local lava_max_height = vmg.define("lava_max_height", -1)
local altitude_chill = vmg.define("altitude_chill", 90)
local do_caves = vmg.define("caves", true)
local simple_caves = vmg.define("simple_caves", false)
local do_cave_stuff = vmg.define("cave_stuff", false)
local dry_rivers = vmg.define("dry_rivers", false)
local average_stone_level = vmg.define("average_stone_level", 180)
local dirt_reduction = math.sqrt(average_stone_level) / (vmg.noises[7].offset - 0.5) -- Calculate dirt_reduction such as v7 - sqrt(average_stone_level) / dirt_reduction = 0.5 on average. This means that, on average at y = average_stone_level, dirt_thickness = 0.5 (half of the surface is bare stone)
local average_snow_level = vmg.define("average_snow_level", 100)
local snow_threshold = vmg.noises[17].offset * 0.5 ^ (average_snow_level / altitude_chill)
local dry_dirt_threshold = vmg.define("dry_dirt_threshold", 0.6)
local player_max_distance = vmg.define("player_max_distance", 450)
local clay_threshold = vmg.define("clay_threshold", 1)
local silt_threshold = vmg.define("silt_threshold", 1)
local sand_threshold = vmg.define("sand_threshold", 0.75)
local dirt_threshold = vmg.define("dirt_threshold", 0.5)
local water_level = vmg.define("water_level", 1)
local river_water = vmg.define("river_water", true)
local ores = vmg.define("ores", true)
-- Register ores
-- We need more types of stone than just gray. Fortunately, there are
-- two available already. Sandstone forms in layers. Desert stone...
-- doesn't exist, but let's assume it's another sedementary rock
-- and place it similarly. -- djr
if vmg.define("stone_ores", true) then
minetest.register_ore({ore_type="sheet", ore="default:sandstone", wherein="default:stone", clust_num_ores=250, clust_scarcity=60, clust_size=10, y_min=-1000, y_max=31000, noise_threshhold=0.1, noise_params={offset=0, scale=1, spread={x=256, y=256, z=256}, seed=4130293965, octaves=5, persist=0.60}, random_factor=1.0})
minetest.register_ore({ore_type="sheet", ore="default:desert_stone", wherein="default:stone", clust_num_ores=250, clust_scarcity=60, clust_size=10, y_min=-1000, y_max=31000, noise_threshhold=0.1, noise_params={offset=0, scale=1, spread={x=256, y=256, z=256}, seed=163281090, octaves=5, persist=0.60}, random_factor=1.0})
end
-- TERRAIN SHAPE ACCORDING TO NOISE VALUES
-- Reurns 3 parameters:
-- mountain_ground: Height of the mountains where v5 = 0. v5 makes the ground level vary around this value.
-- slopes: Intensity of the variations around mountain_ground, basically how chaotic the terrain will be.
-- river: whether there is a river here
-- This function is executed for every vertical row in the case of 2D noise rivers, and for every node for 3D noise rivers. It allows
local function calculate_terrain_at_point(base_ground, v2, v3, v4, v5)
v2 = math.abs(v2) - river_size -- v2 is distance from a river, so I'd like a positive value.
local river = v2 < 0 -- the rivers are placed where v2 is negative, so where the original v2 value is close to zero.
if river then
local depth = river_depth * math.sqrt(1 - (v2 / river_size + 1) ^ 2) -- use the curve of the function −sqrt(1−x²) which modelizes a circle.
local mountain_ground = math.min(math.max(base_ground - depth, water_level - 6), base_ground)
-- base_ground - depth : height of the bottom of the river
-- water_level - 6 : don't make rivers below 6 nodes under the surface
return mountain_ground, 0, river, v2 -- slopes = 0 because noise #6 has not any influence on rivers
end
local valleys = v3 * (1 - math.exp(- (v2 / v4) ^ 2)) -- use the curve of the function 1−exp(−(x/a)²) to modelise valleys. Making "a" varying 0 < a ≤ 1 changes the shape of the valleys. Try it with a geometry software ! (here x = v2 and a = v4). This variable represents the height of the terrain, from the rivers.
local mountain_ground = base_ground + valleys -- approximate height of the terrain at this point (could be slightly modified by the 3D noise #6)
local slopes = v5 * valleys -- This variable represents the maximal influence of the noise #6 on the elevation. v5 is the rate of the height from rivers (variable "valleys") that is concerned.
return mountain_ground, slopes, river, v2
end
local data = {}
-- THE MAPGEN FUNCTION
function vmg.generate(minp, maxp, seed)
if vmg.registered_on_first_mapgen then -- Run callbacks
for _, f in ipairs(vmg.registered_on_first_mapgen) do
f()
end
vmg.registered_on_first_mapgen = nil
vmg.register_on_first_mapgen = nil
end
-- minp and maxp strings, used by logs
local minps, maxps = minetest.pos_to_string(minp), minetest.pos_to_string(maxp)
if vmg.loglevel >= 2 then
print("[Valleys Mapgen] Preparing to generate map from " .. minps .. " to " .. maxps .. " ...")
elseif vmg.loglevel == 1 then
print("[Valleys Mapgen] Generating map from " .. minps .. " to " .. maxps .. " ...")
end
-- start the timer
local t0 = os.clock()
-- 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.
-- Ground nodes
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_dry = minetest.get_content_id("default:dirt_with_dry_grass")
local c_snow = minetest.get_content_id("default:dirt_with_snow")
local c_dirt_clay = minetest.get_content_id("valleys_mapgen:dirt_clayey")
local c_lawn_clay = minetest.get_content_id("valleys_mapgen:dirt_clayey_with_grass")
local c_dry_clay = minetest.get_content_id("valleys_mapgen:dirt_clayey_with_dry_grass")
local c_snow_clay = minetest.get_content_id("valleys_mapgen:dirt_clayey_with_snow")
local c_dirt_silt = minetest.get_content_id("valleys_mapgen:dirt_silty")
local c_lawn_silt = minetest.get_content_id("valleys_mapgen:dirt_silty_with_grass")
local c_dry_silt = minetest.get_content_id("valleys_mapgen:dirt_silty_with_dry_grass")
local c_snow_silt = minetest.get_content_id("valleys_mapgen:dirt_silty_with_snow")
local c_dirt_sand = minetest.get_content_id("valleys_mapgen:dirt_sandy")
local c_lawn_sand = minetest.get_content_id("valleys_mapgen:dirt_sandy_with_grass")
local c_dry_sand = minetest.get_content_id("valleys_mapgen:dirt_sandy_with_dry_grass")
local c_snow_sand = minetest.get_content_id("valleys_mapgen:dirt_sandy_with_snow")
local c_desert_sand = minetest.get_content_id("default:desert_sand")
local c_sand = minetest.get_content_id("default:sand")
local c_gravel = minetest.get_content_id("default:gravel")
local c_silt = minetest.get_content_id("valleys_mapgen:silt")
local c_clay = minetest.get_content_id("valleys_mapgen:red_clay")
local c_water = minetest.get_content_id("default:water_source")
local c_riverwater = minetest.get_content_id("default:river_water_source")
local c_lava = minetest.get_content_id("default:lava_source")
local c_snow_layer = minetest.get_content_id("default:snow")
local c_glowing_fungal_stone = minetest.get_content_id("valleys_mapgen:glowing_fungal_stone")
local c_stalactite = minetest.get_content_id("valleys_mapgen:stalactite")
local c_stalagmite = minetest.get_content_id("valleys_mapgen:stalagmite")
-- Mushrooms
local c_huge_mushroom_cap = minetest.get_content_id("valleys_mapgen:huge_mushroom_cap")
local c_giant_mushroom_cap = minetest.get_content_id("valleys_mapgen:giant_mushroom_cap")
local c_giant_mushroom_stem = minetest.get_content_id("valleys_mapgen:giant_mushroom_stem")
local c_mushroom_fertile_red = minetest.get_content_id("flowers:mushroom_fertile_red")
local c_mushroom_fertile_brown = minetest.get_content_id("flowers:mushroom_fertile_brown")
-- Air and Ignore
local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore")
-- The VoxelManipulator, a complicated but speedy method to set many nodes at the same time
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
vm:get_data(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 a = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local ystride = a.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 chulens = vector.add(vector.subtract(maxp, minp), 1) -- Size of the generated area, used by noisemaps
local chulens_sup = {x = chulens.x, y = chulens.y + 6, z = chulens.z} -- for the noise #6 that needs extra values
local minp2d = pos2d(minp)
-- Mapgen preparation is now finished. Check the timer to know the elapsed time.
local t1 = os.clock()
if vmg.loglevel >= 2 then
print("[Valleys Mapgen] Mapgen preparation finished in " .. displaytime(t1-t0))
print("[Valleys Mapgen] Calculating noises ...")
end
-- Calculate the noise values
local n1 = vmg.noisemap(1, minp2d, chulens)
local n2 = use_3d_rivers and vmg.noisemap(2, minp, chulens_sup) or vmg.noisemap(2, minp2d, chulens) -- Select parameters according to 3d_rivers
local n3 = vmg.noisemap(3, minp2d, chulens)
local n4 = vmg.noisemap(4, minp2d, chulens)
local n5 = vmg.noisemap(5, minp2d, chulens)
local n6 = vmg.noisemap(6, minp, chulens_sup)
local n7 = vmg.noisemap(7, minp2d, chulens)
local n8
local n9
local n10
local n11
local n12
local n19 -- It's more convenient to put these here with the other caves.
local n20
if do_caves then
if simple_caves then
n19 = vmg.noisemap(19, minp, chulens)
n20 = vmg.noisemap(20, minp, chulens)
else
n8 = vmg.noisemap(8, minp, chulens)
n9 = vmg.noisemap(9, minp, chulens)
n10 = vmg.noisemap(10, minp, chulens)
n11 = vmg.noisemap(11, minp, chulens)
n12 = vmg.noisemap(12, minp, chulens)
end
end
local n13 = vmg.noisemap(13, minp2d, chulens)
local n14 = vmg.noisemap(14, minp2d, chulens)
local n15 = vmg.noisemap(15, minp2d, chulens)
local n16 = vmg.noisemap(16, minp2d, chulens)
-- Noise #17 is not used this way
local n18 = vmg.noisemap(18, minp2d, chulens)
-- After noise calculation, check the timer
local t2 = os.clock()
if vmg.loglevel >= 2 then
print("[Valleys Mapgen] Noises calculation finished in " .. displaytime(t2-t1))
print("[Valleys Mapgen] Collecting data ...")
end
-- THE CORE OF THE MOD: THE MAPGEN ALGORITHM ITSELF
-- indexes for noise arrays
local i2d = 1 -- index for 2D noises
local i3d_sup = 1 + chulens.x * (chulens.y+5) -- index for noise #2 and #6 which have a special size
local i3d = 1 + chulens.x * (chulens.y-1) -- index for other 3D noises
-- Calculate increments
local i2d_incrZ = chulens.x
local i2d_incrX = 1 - chulens.x * chulens.z
local i3d_incrY = - chulens.x
local i3d_incrZ = 2 * chulens.x * chulens.y
local i3d_sup_incrZ = 2 * chulens.x * (chulens.y+6)
local i3d_incrX = 1 - chulens.x * chulens.y * chulens.z
local i3d_sup_incrX = 1 - chulens.x * (chulens.y + 6) * chulens.z
local last_cave_block = {nil,nil,nil}
for x = minp.x, maxp.x do -- for each YZ plane
for z = minp.z, maxp.z do -- for each vertical line in this plane
local v1, v3, v4, v5, v7, v13, v14, v15, v16, v18 = n1[i2d], n3[i2d], n4[i2d], n5[i2d], n7[i2d], n13[i2d], n14[i2d], n15[i2d], n16[i2d], n18[i2d] -- take the noise values for 2D noises
v3 = v3 ^ 2 -- The square function changes the behaviour of this noise : very often small, and sometimes very high.
local base_ground = v1 + v3 -- v3 is here because terrain is generally higher where valleys are deep (mountains). base_ground represents the height of the rivers, most of the surface is above.
local v2, mountain_ground, slopes, river
if not use_3d_rivers then
v2 = n2[i2d]
mountain_ground, slopes, river, v2 = calculate_terrain_at_point(base_ground, v2, v3, v4, v5)
end
-- Choose biome, by default normal dirt
local dirt = c_dirt
local lawn = c_lawn
local dry = c_dry
local snow = c_snow
local max = math.max(v13, v14, v15) -- the biome is the maximal of these 3 values.
if max > dirt_threshold then -- if one of these values is bigger than dirt_threshold, make clayey, silty or sandy dirt, depending on the case. If none of clay, silt or sand is predominant, make normal dirt.
if v13 == max then
if v13 > clay_threshold then
dirt = c_clay
lawn = c_clay
dry = c_clay
snow = c_clay
else
dirt = c_dirt_clay
lawn = c_lawn_clay
dry = c_dry_clay
snow = c_snow_clay
end
elseif v14 == max then
if v14 > silt_threshold then
dirt = c_silt
lawn = c_silt
dry = c_silt
snow = c_silt
else
dirt = c_dirt_silt
lawn = c_lawn_silt
dry = c_dry_silt
snow = c_snow_silt
end
else
if v15 > sand_threshold then
dirt = c_desert_sand
lawn = c_desert_sand
dry = c_desert_sand
snow = c_desert_sand
else
dirt = c_dirt_sand
lawn = c_lawn_sand
dry = c_dry_sand
snow = c_snow_sand
end
end
end
local is_beach = v15 > 0 and v16 > 0 -- 2 conditions must been met to make possible the beach.
local beach = v15 * v16 + water_level -- the y coordinate below which dirt is replaced by beach sand. So if the terrain is higher, there is no beach.
-- raw humidity, see below at vmg.get_humidity
local hraw = 2 ^ (v13 - v15 + v18 * 2)
-- After base_ground is used for terrain, modify it by humidity
-- to make rivers dry up in deserts.
if dry_rivers and hraw < 1 then -- average humidity?
base_ground = base_ground + (hraw - 1) * river_depth
end
local column = {}
for y = maxp.y + 6, minp.y, -1 do -- for each node in vertical line
local ivm = a:index(x, y, z) -- index of the data array, matching the position {x, y, z}
local v6 = n6[i3d_sup] -- take the noise values for 3D noises
local v8, v9, v10, v11, v12
if use_3d_rivers then
v2 = n2[i3d_sup]
mountain_ground, slopes, river, v2 = calculate_terrain_at_point(base_ground, v2, v3, v4, v5)
end
local in_ground = v6 * slopes > y - mountain_ground
column[y] = in_ground
if y <= maxp.y then
if in_ground then -- if pos is in the ground
local is_cave = false
local sr, v19, v20
if do_caves then
if simple_caves then
v19, v20 = n19[i3d], n20[i3d] -- take the noise values for 3D noises
local n1 = (math.abs(v19) < 0.07)
local n2 = (math.abs(v20) < 0.07)
is_cave = n1 and n2
sr = math.floor((v19 + v20) * 100000) % 1000
else
v8, v9, v10, v11, v12 = n8[i3d], n9[i3d], n10[i3d], n11[i3d], n12[i3d] -- take the noise values for 3D noises
is_cave = v8 ^ 2 + v9 ^ 2 + v10 ^ 2 + v11 ^ 2 < caves_size -- The 4 cave noises must be close to zero to produce a cave. The square is used for 2 reasons : we need positive values, and, for mathematical reasons, it results in more circular caves.
end
end
if not is_cave then -- if pos is not inside a cave
local thickness = v7 - math.sqrt(math.abs(y)) / dirt_reduction -- Calculate dirt thickness, according to noise #7, dirt reduction parameter, and elevation (y coordinate)
local above = math.floor(thickness + math.random()) -- The following code will look for air at this many nodes up. If any, make dirt, else, make stone. So, it's the dirt layer thickness. An "above" of zero = bare stone.
above = math.max(above, 0) -- must be positive
if y >= water_level and not column[y+1] and not river then -- If node above is not in the ground
if is_beach and y < beach then -- if beach, make sand
data[ivm] = c_sand
else -- place lawn
-- calculate humidity, see below at vmg.get_humidity
local soil_humidity = hraw * (1 - math.exp(-thickness - 0.5))
local sea_water = 0.5 ^ math.max((y - water_level) / 6, 0)
local river_water = 0.5 ^ math.max((y - base_ground) / 3, 0)
local water = sea_water + (1 - sea_water) * river_water
local humidity = soil_humidity * (1 + water)
local ivm2 = ivm + ystride -- index of the node above
y = y + 1
local pos = {x = x, y = y, z = z}
local v17 = vmg.get_noise(pos, 17) -- Noise #17 is used this way : that's a 3D noise, so a noisemap would be heavy, and less than 2% would be used, contrary to other 3D noises. So it's faster to calculate it node per node, only when needed.
local temp -- calculate_temperature for node above, see below at vmg.get_temperature
if y > 0 then
temp = v17 * 0.5 ^ (y / altitude_chill) -- Divide temperature noise by 2 by climbing altitude_chill
else
temp = v17 * 0.5 ^ (-y / altitude_chill) + 20 * (v12 + 1) * (1 - 2 ^ (y / lava_depth))
end
if temp > snow_threshold then -- If temperature is too high for snow
if above > 0 then
if humidity <= dry_dirt_threshold then
data[ivm] = dry
else
data[ivm] = lawn
end
else
data[ivm] = c_stone
end
else -- Snow
if above > 0 then
data[ivm] = snow -- dirt with snow
else
data[ivm] = c_stone
end
data[ivm2] = c_snow_layer -- set node above to snow
end
if above > 0 then
local conditions = { -- pack it in a table, for plants API
v1 = v1,
v2 = v2,
v3 = v3,
v4 = v4,
v5 = v5,
v6 = v6,
v7 = v7,
v8 = v8,
v9 = v9,
v10 = v10,
v11 = v11,
v12 = v12,
v13 = v13,
v14 = v14,
v15 = v15,
v16 = v16,
v17 = v17,
v18 = v18,
v19 = v19,
v20 = v20,
temp = temp,
humidity = humidity,
sea_water = sea_water,
river_water = river_water,
water = water,
thickness = thickness
}
vmg.choose_generate_plant(conditions, pos, data, a, ivm2)
end
y = y - 1
end
elseif not column[y+above] then -- if node at "above" nodes up is not in the ground, make dirt
if is_beach and y < beach then
data[ivm] = c_sand
else
data[ivm] = dirt
end
else
if do_cave_stuff and x == last_cave_block[1] and z == last_cave_block[3] and y == last_cave_block[2] + 1 and math.random() < 0.13 then
if data[ivm - ystride] == c_air and math.random() < 0.75 then
data[ivm] = c_stone
data[ivm - ystride] = c_stalactite
else
local temp = vmg.get_temperature({x=x, y=y, z=z})
if temp > 1.2 and temp < 1.6 then
data[ivm] = c_glowing_fungal_stone
end
end
else
data[ivm] = c_stone
end
end
elseif simple_caves and y <= lava_max_height and sr < math.ceil(-y/10000) and y > minp.y and data[ivm - ystride] == c_stone then
data[ivm] = c_lava
elseif (not simple_caves) and v11 + v12 > 2 ^ (y / lava_depth) and y <= lava_max_height then
data[ivm] = c_lava
elseif do_cave_stuff then
-- mushrooms and water in caves -- djr
last_cave_block = {x,y,z}
-- check how much air we have til we reach stone
local air_to_stone = -1
for i = 1,3 do
local d = data[ivm - (ystride * i)]
if d ~= c_air then
if d == c_stone then
air_to_stone = i
end
break
end
end
local temp = vmg.get_temperature({x=x, y=y, z=z})
if air_to_stone == 1 and math.random() < 0.18 then
local r = math.random()
if r < 0.01 then
data[ivm] = c_riverwater
elseif r < 0.04 then
-- reserved
elseif r < 0.13 and temp > 1.2 and temp < 1.6 then
data[ivm - ystride] = c_dirt
data[ivm] = c_mushroom_fertile_red
elseif r < 0.22 and temp > 1.2 and temp < 1.6 then
data[ivm - ystride] = c_dirt
data[ivm] = c_mushroom_fertile_brown
elseif r < 0.44 then -- leave some extra dirt, for appearances sake
data[ivm - ystride] = c_dirt
else
data[ivm] = c_stalagmite
end
elseif air_to_stone == 2 and temp > 1.2 and temp < 1.6 and math.random() < 0.01 then
data[ivm] = c_huge_mushroom_cap
data[ivm - ystride] = c_giant_mushroom_stem
data[ivm - (ystride * 2)] = c_dirt
elseif air_to_stone == 3 and temp > 1.2 and temp < 1.6 and math.random() < 0.005 then
data[ivm] = c_giant_mushroom_cap
data[ivm - ystride] = c_giant_mushroom_stem
data[ivm - (ystride * 2)] = c_giant_mushroom_stem
data[ivm - (ystride * 3)] = c_dirt
end
end
elseif y <= water_level then -- if pos is not in the ground, and below water_level, it's an ocean
data[ivm] = c_water
elseif river and y + 1 < base_ground then
if river_water then
data[ivm] = c_riverwater
else
data[ivm] = c_water
end
end
i3d = i3d + i3d_incrY -- decrement i3d by one line
end
i3d_sup = i3d_sup + i3d_incrY -- idem
end
i2d = i2d + i2d_incrZ -- increment i2d by one Z
i3d = i3d + i3d_incrZ -- idem for i3d
i3d_sup = i3d_sup + i3d_sup_incrZ -- for i3d_sup, just avoid the 6 supplemental lines
end
i2d = i2d + i2d_incrX -- decrement the Z line previously incremented and increment by one X (1)
i3d = i3d + i3d_incrX -- decrement the YZ plane previously incremented and increment by one X (1)
i3d_sup = i3d_sup + i3d_sup_incrX -- idem, including the supplemental lines
end
vmg.execute_after_mapgen() -- needed for jungletree roots
if darkage_mapgen then -- Compatibility with darkage mod by CraigyDavi. If you see error messages like "WARNING: unknown global variable" at this line, don't worry :)
darkage_mapgen(data, a, minp, maxp, seed)
end
-- After data collecting, check timer
local t3 = os.clock()
if vmg.loglevel >= 2 then
print("[Valleys Mapgen] Data collecting finished in " .. displaytime(t3-t2))
print("[Valleys Mapgen] Writing data ...")
end
-- execute voxelmanip boring stuff to write to the map...
vm:set_data(data)
if ores then
minetest.generate_ores(vm, minp, maxp)
end
vm:set_lighting({day = 0, night = 0})
vm:calc_lighting()
vm:update_liquids()
vm:write_to_map()
-- Now mapgen is finished. What an adventure for just generating a chunk ! I hope your processor is speedy and you have enough RAM !
local t4 = os.clock()
if vmg.loglevel >= 2 then
print("[Valleys Mapgen] Data writing finished in " .. displaytime(t4-t3))
end
if vmg.loglevel >= 1 then
print("[Valleys Mapgen] Mapgen finished in " .. displaytime(t4-t0))
end
table.insert(mapgen_times.preparation, t1 - t0)
table.insert(mapgen_times.noises, t2 - t1)
table.insert(mapgen_times.collecting, t3 - t2)
table.insert(mapgen_times.writing, t4 - t3)
table.insert(mapgen_times.total, t4 - t0)
end
-- Display mapgen stats on shutdown
local function stats(t)
local n = #t
local sum = 0
local sum_sq = 0
for _, k in ipairs(t) do
sum = sum + k
sum_sq = sum_sq + k^2
end
local average = sum / n
local variance = sum_sq / n - average^2
local standard_dev = math.sqrt(variance)
return average, standard_dev
end
minetest.register_on_shutdown(function()
if #mapgen_times.total == 0 then
return
end
if vmg.loglevel >= 1 then
local average, standard_dev
print("[Valleys Mapgen] Mapgen statistics:")
if vmg.loglevel >= 2 then
average, standard_dev = stats(mapgen_times.preparation)
print("[Valleys Mapgen] Mapgen preparation step:")
print(" average " .. displaytime(average))
print(" standard deviation " .. displaytime(standard_dev))
average, standard_dev = stats(mapgen_times.noises)
print("[Valleys Mapgen] Noises calculation step:")
print(" average " .. displaytime(average))
print(" standard deviation " .. displaytime(standard_dev))
average, standard_dev = stats(mapgen_times.collecting)
print("[Valleys Mapgen] Data collecting step:")
print(" average " .. displaytime(average))
print(" standard deviation " .. displaytime(standard_dev))
average, standard_dev = stats(mapgen_times.writing)
print("[Valleys Mapgen] Data writing step:")
print(" average " .. displaytime(average))
print(" standard deviation " .. displaytime(standard_dev))
end
average, standard_dev = stats(mapgen_times.total)
print("[Valleys Mapgen] TOTAL:")
print(" average " .. displaytime(average))
print(" standard deviation " .. displaytime(standard_dev))
end
end)
-- Trees are registered in a separate file
dofile(vmg.path .. "/trees.lua")
dofile(vmg.path .. "/plants_api.lua")
dofile(vmg.path .. "/plants.lua")
function vmg.get_humidity_raw(pos)
local v13 = vmg.get_noise(pos, 13) -- Clayey soil : wetter
local v15 = vmg.get_noise(pos, 15) -- Sandy soil : drier
local v18 = vmg.get_noise(pos, 18) -- Humidity noise
return 2 ^ (v13 - v15 + v18 * 2) -- Make sure that humidity is positive. Humidity is between 0.25 and 16.
end
function vmg.get_humidity(pos)
local y = pos.y
local flatpos = pos2d(pos)
local hraw = vmg.get_humidity_raw(flatpos)
-- Another influence on humidity: Dirt thickness, because when the dirt layer is very thin, the soil is drained.
local v7 = vmg.get_noise(flatpos, 7)
local thickness = math.max(v7 - math.sqrt(math.abs(y)) / dirt_reduction, 0) -- Positive
local soil_humidity = hraw * (1 - math.exp(-thickness - 0.5)) -- Yes I love exponential-like functions. You can modelize whatever you want with exponentials !!!
-- Get base ground level to know the river level that influences humidity
local v1 = vmg.get_noise(flatpos, 1)
local v3 = vmg.get_noise(flatpos, 3) ^ 2
local base_ground = v1 + v3
local sea_water = 0.5 ^ math.max((y - water_level) / 6, 0) -- At the sea level, sea_water is 1. Every 6 nodes height divide it by 2.
local river_water = 0.5 ^ math.max((y - base_ground) / 3, 0) -- At the river level, river_water is 1. Every 3 nodes height divide it by 2.
local water = sea_water + (1 - sea_water) * river_water -- A simple sum is not satisfactory, because it may be bigger than 1.
return soil_humidity * (1 + water)
end
function vmg.test_dry(pos)
return vmg.get_humidity(pos) <= dry_dirt_threshold
end
function vmg.get_temperature(pos)
local v12 = vmg.get_noise(pos, 12) + 1 -- Lava noise for underground
local v17 = vmg.get_noise(pos, 17) -- Climate noise
local y = pos.y
if y > 0 then
return v17 * 0.5 ^ (y / altitude_chill) -- Divide v17 by 2 by climbing "altitude_chill" nodes
else
return v17 * 0.5 ^ (-y / altitude_chill) + 20 * v12 * (1 - 2 ^ (y / lava_depth)) -- Underground, v17 less and less matter. So, gradually replace it by another calculation method, based on lava. Sorry: I don't remember the sense of this code :/
end
end
function vmg.test_snow(pos)
return vmg.get_temperature(pos) <= snow_threshold
end
function vmg.get_noise(pos, i)
local n = vmg.noises[i]
local noise = minetest.get_perlin(n)
if not pos.z then -- 2D noise
return noise:get2d(pos)
else -- 3D noise
return noise:get3d(pos)
end
end
local function round(n)
return math.floor(n + 0.5)
end
function vmg.get_elevation(pos)
local v1 = vmg.get_noise(pos, 1) -- base ground
local v2, mountain_ground, slopes, river
local v3 = vmg.get_noise(pos, 3) ^ 2 -- valleys depth
local v4 = vmg.get_noise(pos, 4) -- valleys profile
local v5 = vmg.get_noise(pos, 5) -- inter-valleys slopes
local base_ground = v1 + v3
local epos = pos3d(pos, math.ceil(base_ground)) -- Position with elevation (3D)
v2 = vmg.get_noise(use_3d_rivers and epos or pos, 2) -- Use 2D or 3D position according to use_3d_rivers
mountain_ground, slopes, river = calculate_terrain_at_point(base_ground, v2, v3, v4, v5) -- Calculate terrain parameters
if river then
return math.ceil(base_ground), true
end
epos = pos3d(pos, math.ceil(mountain_ground)) -- set epos to mountain_ground (most probable ground level) to optimize
if use_3d_rivers then
v2 = vmg.get_noise(epos, 2)
mountain_ground, slopes, river = calculate_terrain_at_point(base_ground, v2, v3, v4, v5) -- Recalculate
end
if vmg.get_noise(epos, 6) * slopes > epos.y - mountain_ground then -- Position is in the ground, so look for air higher
repeat
epos.y = epos.y + 1
if use_3d_rivers then
v2 = vmg.get_noise(epos, 2)
mountain_ground, slopes, river = calculate_terrain_at_point(base_ground, v2, v3, v4, v5)
end
until vmg.get_noise(epos, 6) * slopes <= epos.y - mountain_ground -- End of the loop when there is air
return epos.y, river -- Return position of the first air node
else -- Position is not in the ground, so look for dirt lower
repeat
epos.y = epos.y - 1
if use_3d_rivers then
v2 = vmg.get_noise(epos, 2)
mountain_ground, slopes, river = calculate_terrain_at_point(base_ground, v2, v3, v4, v5)
end
until vmg.get_noise(epos, 6) * slopes > epos.y - mountain_ground -- End of the loop when there is dirt (or any ground)
return epos.y+1, river -- Return position of the first air node
end
end
function vmg.spawnplayer(player)
-- Choose a point to spawn the player, from an angle, and a distance from (0;0)
local angle = math.random() * math.pi * 2
local distance = math.random() * player_max_distance
local p_angle = {x = math.cos(angle), y = math.sin(angle)} -- Get a position on the trigonometric circle. This position is exactely at 1 unit from (0;0)
local pos = {x = p_angle.x * distance, y = p_angle.y * distance} -- Multiply it by distance, to get the position that meets angle and distance
local elevation, river = vmg.get_elevation(pos) -- get elevation from the previous function
while elevation < water_level + 2 or river do -- If there is water, choose another point
-- Move the position by one unit, to (0;0) to avoid spawning farther than player_max_distance.
pos.x = pos.x - p_angle.x
pos.y = pos.y - p_angle.y
-- and check again
elevation, river = vmg.get_elevation({x = round(pos.x), y = round(pos.y)})
end -- end of the loop when pos is not in the water
pos = {x = round(pos.x), y = round(elevation + 1), z = round(pos.y)} -- Round position and add elevation
player:setpos(pos)
return true -- Disable default player spawner
end