1 local S
= minetest
.get_translator("perlin_explorer")
2 local F
= minetest
.formspec_escape
4 -----------------------------
5 -- Variable initialization --
6 -----------------------------
8 local mod_storage
= minetest
.get_mod_storage()
10 -- If true, the Perlin test nodes will support color
11 -- (set to false in case of performance problems)
12 local COLORIZE_NODES
= true
14 -- If true, will use the grayscale color palette.
15 -- If false, will use the default colors.
16 local grayscale_colors
= minetest
.settings
:get_bool("perlin_explorer_grayscale", false)
18 -- Shows a star on generating new noisechunks
19 local mapgen_star
= minetest
.settings
:get_bool("perlin_explorer_mapgen_star", true)
21 -- The number of available test node colors.
22 -- Higher values lead to worse performance but a coarse color scheme.
23 -- This value is only used for performance reason, because Minetest
24 -- has a sharp performance drop when there are many different node colors on screen.
25 -- Consider removing this one when Minetest's performance problem has been solved.
26 local color_count
= tonumber(minetest
.settings
:get("perlin_explorer_color_count")) or 64
27 local color_lookup
= {
28 [256] = 1, -- full color palette
38 -- This value is used for the calculation of the simplified color.
39 local color_precision
= color_lookup
[color_count
]
40 if not color_precision
then
41 color_precision
= 4 -- default: 64 colors
42 minetest
.log("warning", "[perlin_explorer] Invalid setting value specified for 'perlin_explorer_color_count'! Using the default ...")
45 -- Time to wait in seconds before checking and generating new nodes in autobuild mode.
46 local AUTOBUILD_UPDATE_TIME
= 0.1
48 -- x/y/z size of "noisechunks" (like chunks in the Minetest mapgen, but specific to this
49 -- mod) to generate in autobuild mode.
50 local AUTOBUILD_SIZE
= 16
51 -- Amount of noisechunks to generate around player
52 local AUTOBUILD_CHUNKDIST
= 2
55 -- Color of the formspec box[] element
56 local FORMSPEC_BOX_COLOR
= "#00000080"
57 -- Color for the section titles in the formspec
58 local FORMSPEC_HEADER_COLOR
= "#000000FF"
59 -- Color of the diagram lines of the histogram
60 local FORMSPEC_HISTOGRAM_LINE_COLOR
= "#FFFFFF80"
61 -- Color of hisogram bars
62 local FORMSPEC_HISTOGRAM_BAR_COLOR
= "#00FF00FF"
64 -- Number of data buckets to use for the histogram
65 local HISTOGRAM_BUCKETS
= 10
67 -- Number of values to pick in a statistics run
68 local STATISTICS_ITERATIONS
= 1000000
70 -- This number is used to pick a reasonable size of the area
71 -- over which to calculate statistics on. The size (side length
72 -- of the area/volume, to be precise)
73 -- is the maximum spread (of the 2 or 3 axes) times this number.
74 -- This is needed so the randomly picked values in that
75 -- area are more evenly distributed and lot heavily
76 -- localized (which would skew the stats).
77 -- If this is too small, the stats will be too localized, but
78 -- if this is too large, it will limit the maximum supported
80 local STATISTICS_SPREAD_FACTOR
= 69
81 -- Maximum size of the statistics calculation area (side length of square area /
82 -- cube volume). Must make sure that Minetest still accepts coordinates
84 local STATISTICS_MAX_SIZE
= math
.floor((2^
32-1)/1000)
85 -- Maximum allowed spread for statistics to still be supported.
86 -- Used for triggering error message.
87 local STATISTICS_MAX_SPREAD
= math
.floor(STATISTICS_MAX_SIZE
/ STATISTICS_SPREAD_FACTOR
)
89 -- Hardcoded message text
90 local TEXT_ERROR_BAD_WAVELENGTH
= S("Bad noise parameters given. At least one of the resulting wavelengths got below 1, which is not allowed. Decrease the octaves or lacunarity, or increase the spread to reach valid wavelengths. Use the “Analyze” feature to see the wavelengths.")
92 -- Per-player formspec states (mostly for remembering checkbox states)
93 local formspec_states
= {}
95 -- List of noise parameters profiles
96 -- Format of a single profile:
97 -- * noiseparams: Noise parameters table
98 -- * name: Name as it appears in list (not set for user profiles)
100 -- * "active": Active noise parameters. Not deletable
101 -- * "mapgen": Noise parameters loaded from Minetest settings. Not deletable
102 -- * "user": Profiles created by user. Deletable
103 local np_profiles
= {}
105 -- The default noiseparams are used as the initial noiseparams
106 -- or as fallback when stuff fails.
107 local default_noiseparams
= {
110 spread
= vector
.new(10, 10, 10),
118 -- Get default noise params by setting (if available)
119 local np_by_setting
= minetest
.settings
:get_np_group("perlin_explorer_default_noiseparams")
120 if np_by_setting
then
121 default_noiseparams
= np_by_setting
122 minetest
.log("info", "[perlin_explorer] default noiseparams read from setting")
125 -- Holds the currently used Perlin noise
126 local current_perlin
= {}
127 -- holds the current PerlinNoise object
128 current_perlin
.noise
= nil
129 current_perlin
.noiseparams
= table.copy(default_noiseparams
)
131 local noise_settings
= dofile(minetest
.get_modpath(minetest
.get_current_modname()).."/noise_settings_list.lua")
133 -- Add the special "active" profile
134 table.insert(np_profiles
, {noiseparams
=current_perlin
.noiseparams
, name
=S("Active Noise Parameters"), np_type
="active"})
136 -- Add the mapgen setting profiles
137 for n
=1, #noise_settings
do
138 local np
= minetest
.get_mapgen_setting_noiseparams(noise_settings
[n
])
139 -- TODO/FIXME: Make sure that ALL noise settings are gettable (not just those of the active mapgen)
141 table.insert(np_profiles
, {noiseparams
=np
, name
=noise_settings
[n
], np_type
="mapgen"})
145 -- Load user profiles from mod storage
146 minetest
.log("info", "[perlin_explorer] Checking for user profiles in mod storage ...")
147 if mod_storage
:contains("profiles") then
148 local user_profiles_str
= mod_storage
:get_string("profiles")
149 local user_profiles
= minetest
.deserialize(user_profiles_str
)
151 if user_profiles
then
152 for p
=1, #user_profiles
do
153 local user_profile
= user_profiles
[p
]
154 if user_profile
and type(user_profile
) == "table" and user_profile
.noiseparams
then
155 table.insert(np_profiles
, {
157 noiseparams
= user_profiles
[p
].noiseparams
,
161 minetest
.log("warning", "[perlin_explorer] Malformed user profile in mod storage! Skipping ...")
165 minetest
.log("action", "[perlin_explorer] Loaded "..loaded
.." user profile(s) from mod storage")
168 -- Updates the user profiles in the mod stroage.
169 -- Must be called whenever the user profiles change.
170 local function update_mod_stored_profiles()
171 local user_profiles
= {}
172 for p
=1, #np_profiles
do
173 local profile
= np_profiles
[p
]
174 if profile
.np_type
== "user" then
175 table.insert(user_profiles
, {
176 noiseparams
= table.copy(profile
.noiseparams
),
180 local serialized_profiles
= minetest
.serialize(user_profiles
)
181 mod_storage
:set_string("profiles", serialized_profiles
)
182 minetest
.log("info", "[perlin_explorer] Profiles in mod storage updated")
185 -- Options are settings that dictate how the noise is supposed
187 local current_options
= {}
188 -- Side length of calculated perlin area
189 current_options
.size
= 64
191 -- Noise value that will be represented by the minimum color
192 current_options
.min_color
= -1.0
193 -- Noise value that will be represented by the maximum color
194 current_options
.max_color
= 1.0
195 -- Noise value that is interpreted as the midpoint for node colorization
196 current_options
.mid_color
= 0.0
198 -- Currently selected nodetype for the mapgen (see `nodetypes` table)
199 current_options
.nodetype
= 1
201 -- Currently selected build mode for the mapgen (see below)
202 -- These modes are available:
203 -- * Auto (1): same as ALL in 2D mode, same as HIGH_ONLY in 3D mode
204 -- * All (2): build nodes for all noise values
205 -- * High only (3): build nodes for high noise values only
206 -- * Low only (4): build nodes for low noise values only
208 local BUILDMODE_AUTO
= 1
209 local BUILDMODE_ALL
= 2
210 local BUILDMODE_HIGH_ONLY
= 3
211 local BUILDMODE_LOW_ONLY
= 4
212 local BUILDMODES_COUNT
= 4
213 current_options
.buildmode
= BUILDMODE_AUTO
215 -- dimensions of current Perlin noise (2 or 3)
216 current_options
.dimensions
= 2
217 -- If greater than 1, the Perlin noise values are "pixelized". Noise values at
218 -- coordinates not divisible by sidelen will be set equal to the noise value
219 -- of the nearest number (counting downwards) that is divisible by sidelen.
220 -- This is (kind of) analogous to the "sidelen" parameter of mapgen decorations.
221 current_options
.sidelen
= 1
222 -- Place position of current perlin (relevant for single placing)
223 current_options
.pos
= nil
225 -- If enabled, automatically generate nodes around player
226 current_options
.autogen
= false
228 -- Remember which areas have been loaded by the autogen so far
229 -- Index: Hash of node position, value: true if loaded
230 local loaded_areas
= {}
232 ----------------------
233 -- Helper functions --
234 ----------------------
236 -- Reduce the pos coordinates down to the closest numbers divisible by sidelen
237 local sidelen_pos
= function(pos
, sidelen
)
238 local newpos
= {x
=pos
.x
, y
=pos
.y
, z
=pos
.z
}
242 newpos
.x
= newpos
.x
- newpos
.x
% sidelen
243 newpos
.y
= newpos
.y
- newpos
.y
% sidelen
244 newpos
.z
= newpos
.z
- newpos
.z
% sidelen
248 -- A hack to force a formspec to be unique.
249 -- Appends invisible filler content, changing
250 -- every time this function is called.
251 -- This will force the formspec to be unique.
252 -- Neccessary because Minetest doesn’t update
253 -- the formspec when the same formspec was
254 -- sent twice. This is a problem because the
255 -- player might have edited a text field, causing
256 -- the formspec to not properly update.
257 -- Only use this function for one formspec
258 -- type, otherwise it won’t work.
259 -- Workaround confirmed working for: Minetest 5.5.0
260 -- FIXME: Drop this when Minetest no longer does
262 local unique_formspec_spaces
= function(player_name
, formspec
)
263 -- Increase the sequence number every time
264 -- this thing is used, causing the number
265 -- of spaces to change
266 local seq
= formspec_states
[player_name
].sequence_number
268 local filler
= string.rep(" ", seq
)
269 formspec_states
[player_name
].sequence_number
= seq
270 return formspec
.. filler
273 -- Takes the 3 noise flags default/eased/absvalue (either true or false)
274 -- and converts them to a string
275 local build_flags_string
= function(defaults
, eased
, absvalue
)
278 table.insert(flagst
, "defaults")
281 table.insert(flagst
, "eased")
283 table.insert(flagst
, "noeased")
286 table.insert(flagst
, "absvalue")
288 table.insert(flagst
, "noabsvalue")
290 local flags
= table.concat(flagst
, ",")
293 -- Takes a flags string (in the noiseparams format)
294 -- and returns a table of the form {
295 -- defaults = true/false
296 -- eased = true/false
297 -- absvalue = true/false
299 local parse_flags_string
= function(flags
)
300 local ftable
= string.split(flags
, ",")
301 local defaults
, eased
, absvalue
= false, false, false
303 local s
= string.trim(ftable
[f
])
304 if s
== "defaults" then
306 elseif s
== "eased" then
308 elseif s
== "absvalue" then
312 if not defaults
and not eased
and not absvalue
then
315 return { defaults
= defaults
, eased
= eased
, absvalue
= absvalue
}
318 -- Sets the currently active Perlin noise.
319 -- * noiseparams: NoiseParams table (see Minetest's Lua API documentation)
320 local set_perlin_noise
= function(noiseparams
)
321 current_perlin
.noise
= PerlinNoise(noiseparams
)
322 current_perlin
.noiseparams
= noiseparams
325 -- Register list of node types for test mapgen
327 -- { Entry name for sformspec, high value node, low value node, supports color? }
328 { S("Solid Nodes"), "perlin_explorer:node", "perlin_explorer:node_low", true },
329 { S("Grid Nodes"), "perlin_explorer:grid", "perlin_explorer:grid_low", true },
330 { S("Minibox Nodes"), "perlin_explorer:mini", "perlin_explorer:mini_low", true },
333 -- Analyze the given noiseparams for interesting properties.
334 -- Returns: <min>, <max>, <waves>, <bad_wavelength>
335 -- min = minimum possible value
336 -- max = maximum possible value
337 -- waves = table with x/y/z indices, each containing a list of effective "wavelengths" for each of the axes
338 -- bad_wavelength = true if any wavelength is lower than 1
339 local analyze_noiseparams
= function(noiseparams
)
340 local np
= noiseparams
342 local flags
= parse_flags_string(noiseparams
.flags
)
343 local is_absolute
= flags
.absvalue
== true
344 -- Calculate min. and max. possible values
346 local o_min
, o_max
= 0, 0
347 for o
=1, np
.octaves
do
349 o_max
= o_max
+ (1 * np
.persistence ^
exp)
350 if not is_absolute
then
351 o_min
= o_min
+ (- 1 * np
.persistence ^
exp)
352 -- Note: If absvalue flag is set, the sum of the octaves
353 -- is always 0, so we don't need to calculate it
356 -- Add offset and scale to min/max value (final step)
357 local min_value
= np
.offset
+ np
.scale
* o_min
358 local max_value
= np
.offset
+ np
.scale
* o_max
360 -- Bring the 2 values in the correct order
361 -- (min_value might be bigger for negative scale)
362 if min_value
> max_value
then
363 min_value
, max_value
= max_value
, min_value
366 local bad_wavelength
= false
367 -- Calculate "wavelengths"
368 local axes
= { "x", "y", "z" }
373 local wave
= np
.spread
[w
]
374 for o
=1, np
.octaves
do
376 bad_wavelength
= true
378 table.insert(waves
[w
], wave
)
379 wave
= wave
* (1 / np
.lacunarity
)
382 return min_value
, max_value
, waves
, bad_wavelength
385 -- Add stone node to nodetypes, if present
386 minetest
.register_on_mods_loaded(function()
387 local stone
= minetest
.registered_aliases
["mapgen_stone"]
389 local desc
= minetest
.registered_nodes
[stone
].description
393 table.insert(nodetypes
, { desc
, stone
, "air", false})
401 -- Add a bunch of nodes to generate a map based on a perlin noise.
402 -- Used for visualization.
403 -- Each nodes comes in a "high" and "low" variant.
404 -- high/low means it represents high/low noise values.
405 local paramtype2
, palette
406 if COLORIZE_NODES
then
409 if grayscale_colors
then
410 palette
= "perlin_explorer_node_palette_gray.png"
411 palette_low
= "perlin_explorer_node_palette_gray_low.png"
413 palette
= "perlin_explorer_node_palette.png"
414 palette_low
= "perlin_explorer_node_palette_low.png"
417 -- Solid nodes: Visible, walkable, opaque
418 minetest
.register_node("perlin_explorer:node", {
419 description
= S("Solid Perlin Test Node (High Value)"),
421 -- Intentionally does not cast shadow so that
422 -- cave structures are always fullbright when
424 sunlight_propagates
= true,
425 paramtype2
= paramtype2
,
426 tiles
= {"perlin_explorer_node.png"},
428 groups
= { dig_immediate
= 3 },
429 -- Force-drop without metadata to avoid spamming the inventory
430 drop
= "perlin_explorer:node",
432 minetest
.register_node("perlin_explorer:node_low", {
433 description
= S("Solid Perlin Test Node (Low Value)"),
435 sunlight_propagates
= true,
436 paramtype2
= paramtype2
,
437 tiles
= {"perlin_explorer_node_low.png"},
438 palette
= palette_low
,
439 groups
= { dig_immediate
= 3 },
440 -- Force-drop without metadata to avoid spamming the inventory
441 drop
= "perlin_explorer:node_low",
444 -- Grid nodes: See-through, walkable. Looks like glass.
445 -- Useful to see "inside" of 3D blobs.
446 minetest
.register_node("perlin_explorer:grid", {
447 description
= S("Grid Perlin Test Node (High Value)"),
449 drawtype
= "allfaces",
450 use_texture_alpha
= "clip",
451 sunlight_propagates
= true,
452 paramtype2
= paramtype2
,
453 tiles
= {"perlin_explorer_grid.png"},
454 palette
= "perlin_explorer_node_palette.png",
456 groups
= { dig_immediate
= 3 },
457 drop
= "perlin_explorer:grid",
459 minetest
.register_node("perlin_explorer:grid_low", {
460 description
= S("Grid Perlin Test Node (Low Value)"),
462 drawtype
= "allfaces",
463 sunlight_propagates
= true,
464 paramtype2
= paramtype2
,
465 tiles
= {"perlin_explorer_grid_low.png"},
466 use_texture_alpha
= "clip",
467 palette
= palette_low
,
468 groups
= { dig_immediate
= 3 },
469 drop
= "perlin_explorer:grid_low",
472 -- Minibox nodes: See-through, non-walkable, climbable.
473 -- Looks like dot clouds in large numbers.
474 -- Useful to see and move "inside" of 3D blobs.
475 minetest
.register_node("perlin_explorer:mini", {
476 description
= S("Minibox Perlin Test Node (High Value)"),
478 drawtype
= "nodebox",
483 fixed
= { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
485 use_texture_alpha
= "clip",
486 sunlight_propagates
= true,
487 paramtype2
= paramtype2
,
488 tiles
= {"perlin_explorer_mini.png"},
490 groups
= { dig_immediate
= 3 },
491 drop
= "perlin_explorer:mini",
493 minetest
.register_node("perlin_explorer:mini_low", {
494 description
= S("Minibox Perlin Test Node (Low Value)"),
496 drawtype
= "nodebox",
501 fixed
= { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
503 sunlight_propagates
= true,
504 paramtype2
= paramtype2
,
505 tiles
= {"perlin_explorer_mini_low.png"},
506 use_texture_alpha
= "clip",
507 palette
= palette_low
,
508 groups
= { dig_immediate
= 3 },
509 drop
= "perlin_explorer:mini_low",
512 -- Helper function for the getter tool. Gets a noise value at pos
513 -- and print is in user's chat.
514 -- * pos: Position to get
515 -- * user: Player object
516 -- * precision: Coordinate precision of output (for minetest.pos_to_string)
517 -- * ptype: One of ...
518 -- "node": Get value at node position
519 -- "player": Get value at player position
520 local print_value
= function(pos
, user
, precision
, ptype
)
522 local getpos
= sidelen_pos(pos
, current_options
.sidelen
)
523 if current_options
.dimensions
== 2 then
524 val
= current_perlin
.noise
:get_2d({x
=getpos
.x
, y
=getpos
.z
})
525 elseif current_options
.dimensions
== 3 then
526 val
= current_perlin
.noise
:get_3d(getpos
)
528 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
532 local color_node
= minetest
.get_color_escape_sequence("#FFD47CFF")
533 local color_player
= minetest
.get_color_escape_sequence("#87FF87FF")
534 local color_end
= minetest
.get_color_escape_sequence("#FFFFFFFF")
535 if ptype
== "node" then
536 msg
= S("Value at @1node@2 pos @3: @4", color_node
, color_end
, minetest
.pos_to_string(pos
, precision
), val
)
537 elseif ptype
== "player" then
538 msg
= S("Value at @1player@2 pos @3: @4", color_player
, color_end
, minetest
.pos_to_string(pos
, precision
), val
)
540 error("[perlin_explorer] Invalid ptype in print_value()!")
542 minetest
.chat_send_player(user
:get_player_name(), msg
)
545 -- Get Perlin value of player pos (on_use callback)
546 local use_getter
= function(itemstack
, user
, pointed_thing
)
550 local privs
= minetest
.get_player_privs(user
:get_player_name())
551 if not privs
.server
then
552 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
555 if current_perlin
.noise
then
556 local pos
= user
:get_pos()
557 local ctrl
= user
:get_player_control()
559 if not ctrl
.sneak
then
560 pos
= vector
.round(pos
)
563 print_value(pos
, user
, precision
, "player")
565 local msg
= S("No active Perlin noise set. Set one first!")
566 minetest
.chat_send_player(user
:get_player_name(), msg
)
570 -- Get Perlin value of pointed node (on_place callback)
571 local place_getter
= function(itemstack
, user
, pointed_thing
)
575 local privs
= minetest
.get_player_privs(user
:get_player_name())
576 if not privs
.server
then
577 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
580 if current_perlin
.noise
then
581 if pointed_thing
.type ~= "node" then
582 -- No-op for non-nodes
585 local pos
= pointed_thing
.under
586 print_value(pos
, user
, 0, "node")
588 local msg
= S("No active Perlin noise set. Set one first!")
589 minetest
.chat_send_player(user
:get_player_name(), msg
)
593 -- Gets perlin noise value
594 minetest
.register_tool("perlin_explorer:getter", {
595 description
= S("Perlin Value Getter"),
596 _tt_help
= S("Place: Display Perlin noise value of the pointed node position").."\n"..
597 S("Punch: Display Perlin noise value of player position (+Sneak: precise position)"),
598 inventory_image
= "perlin_explorer_getter.png",
599 wield_image
= "perlin_explorer_getter.png",
600 groups
= { disable_repair
= 1 },
602 on_place
= place_getter
,
605 --[[ Calculate the Perlin noise value and optionally generate nodes.
606 * pos: Bottom front left position of where Perlin noise begins
607 * noise: Perlin noise object to use
608 * noiseparams: noise parameters table
609 * options: see at create_perlin function
610 * stats_mode: if true, will only calculate values for statistics but not place any nodes
611 Returns: a stats table of the form
613 min, -- minimum calculated noise value
614 max, -- maximum calculated noise value
615 avg, -- average calculated noise value
616 value_count, -- number of values that were calculated
617 histogram, -- histogram data for the stats screen. A list
618 -- of "buckets", starting with the lower bounds:
620 [1] = <number of values in 1st bucket>,
621 [2] = <number of values in 2nd bucket>,
623 [HISTOGRAM_BUCKETS] = <number of values in last bucket>,
625 histogram_points, -- List of cutoff points for each of the
627 start_pos, -- lower corner of the area that the stats were calculated for
628 end_pos, -- upper corner of the area that the stats were calculated for
630 Returns nil on error.
632 local calculate_noise
= function(pos
, noise
, noiseparams
, options
, stats_mode
)
638 local time1
= minetest
.get_us_time()
647 local endpos
= vector
.add(startpos
, options
.size
-1)
649 local y_max
= endpos
.y
- startpos
.y
650 if options
.dimensions
== 2 then
651 -- We don't need 3rd axis in 2D
658 local vmanip
, emin
, emax
, vdata
, vdata2
, varea
659 local content_test_node
, content_test_node_low
, node_needs_color
660 if not stats_mode
then
661 -- Only needed when we want to place nodes
662 vmanip
= VoxelManip(startpos
, endpos
)
663 emin
, emax
= vmanip
:get_emerged_area()
664 vdata
= vmanip
:get_data()
665 vdata2
= vmanip
:get_param2_data()
666 varea
= VoxelArea
:new({MinEdge
= emin
, MaxEdge
= emax
})
668 content_test_node
= minetest
.get_content_id(nodetypes
[options
.nodetype
][2])
669 content_test_node_low
= minetest
.get_content_id(nodetypes
[options
.nodetype
][3])
670 node_needs_color
= nodetypes
[options
.nodetype
][4]
676 local sum_of_values
= 0
677 stats
.value_count
= 0
680 local min_possible
, max_possible
= analyze_noiseparams(noiseparams
)
681 local cutoff_points
= {}
682 -- Calculate the cutoff points for the histogram so we know in which data bucket
683 -- to put each value into.
684 for d
=1,HISTOGRAM_BUCKETS
do
685 cutoff_points
[d
] = min_possible
+ ((max_possible
-min_possible
) / HISTOGRAM_BUCKETS
) * d
686 stats
.histogram
[d
] = 0
690 if not stats_mode
then
691 -- Initialize Perlin map
692 -- The noise values will come from this (unless in Stats Mode)
693 local perlin_map_object
= PerlinNoiseMap(noiseparams
, size_v
)
694 if options
.dimensions
== 2 then
695 perlin_map
= perlin_map_object
:get_2d_map({x
=startpos
.x
, y
=startpos
.z
})
697 perlin_map
= perlin_map_object
:get_3d_map(startpos
)
703 x_max
= STATISTICS_ITERATIONS
- 1
707 x_max
= endpos
.x
- startpos
.x
708 z_max
= endpos
.z
- startpos
.z
711 -- Main loop (time-critical!)
715 -- Note: This loop has been optimized for speed, so the code
716 -- might not look pretty.
717 -- Be careful of the performance implications when touching this
720 -- Get Perlin value at current pos
722 if not stats_mode
then
728 elseif stats_mode
then
730 x
= math
.random(startpos
.x
, startpos
.x
+options
.size
),
731 y
= math
.random(startpos
.y
, startpos
.y
+options
.size
),
732 z
= math
.random(startpos
.z
, startpos
.z
+options
.size
),
735 -- Apply sidelen transformation (pixelize)
736 local abspos_get
= sidelen_pos(abspos
, options
.sidelen
)
738 x
= abspos_get
.x
- startpos
.x
+ 1,
739 y
= abspos_get
.y
- startpos
.y
+ 1,
740 z
= abspos_get
.z
- startpos
.z
+ 1,
743 -- Finally get the noise value
745 if options
.dimensions
== 2 then
746 if stats_mode
or (indexpos
.x
< 1 or indexpos
.z
< 1) then
747 -- The pixelization can move indexpos below 1, in this case
748 -- we get the perlin value directly because it is outside
749 -- the precalculated map. Performance impact is hopefully
750 -- not too bad because this will only occur at the low
752 -- Ideally, for cleaner code, the precalculated map would
753 -- take this into account as well but this has not
755 -- In stats mode, we always calculate the noise value directy
756 -- because the values are spread out.
757 perlin_value
= noise
:get_2d({x
=abspos_get
.x
, y
=abspos_get
.z
})
759 -- Normal case: Get value from perlin map
760 perlin_value
= perlin_map
[indexpos
.z
][indexpos
.x
]
762 elseif options
.dimensions
== 3 then
763 if stats_mode
or (indexpos
.x
< 1 or indexpos
.y
< 1 or indexpos
.z
< 1) then
765 perlin_value
= noise
:get_3d(abspos_get
)
768 perlin_value
= perlin_map
[indexpos
.z
][indexpos
.y
][indexpos
.x
]
771 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
776 if not stats
.min then
777 stats
.min = perlin_value
778 elseif perlin_value
< stats
.min then
779 stats
.min = perlin_value
781 if not stats
.max then
782 stats
.max = perlin_value
783 elseif perlin_value
> stats
.max then
784 stats
.max = perlin_value
787 for c
=1, HISTOGRAM_BUCKETS
do
788 if perlin_value
< cutoff_points
[c
] or c
>= HISTOGRAM_BUCKETS
then
789 stats
.histogram
[c
] = stats
.histogram
[c
] + 1
794 sum_of_values
= sum_of_values
+ perlin_value
795 stats
.value_count
= stats
.value_count
+ 1
797 -- This section will set the node
798 if not stats_mode
then
799 -- Calculate color (param2) for node
800 local zeropoint
= options
.mid_color
801 local min_size
= zeropoint
- options
.min_color
802 local max_size
= options
.max_color
- zeropoint
803 local node_param2
= 0
804 if node_needs_color
then
805 if perlin_value
>= zeropoint
then
806 node_param2
= ((perlin_value
- zeropoint
) / max_size
) * 255
808 node_param2
= ((zeropoint
- perlin_value
) / min_size
) * 255
810 node_param2
= math
.floor(math
.abs(node_param2
))
811 node_param2
= math
.max(0, math
.min(255, node_param2
))
812 if node_param2
< 255 then
813 node_param2
= node_param2
- (node_param2
% color_precision
)
818 local index
= varea
:indexp(abspos
)
823 -- Set node and param2 in vmanip
824 if perlin_value
>= zeropoint
then
825 if options
.buildmode
== BUILDMODE_ALL
or options
.buildmode
== BUILDMODE_HIGH_ONLY
or options
.buildmode
== BUILDMODE_AUTO
then
826 vdata
[index
] = content_test_node
827 vdata2
[index
] = node_param2
829 vdata
[index
] = minetest
.CONTENT_AIR
833 if options
.buildmode
== BUILDMODE_ALL
or options
.buildmode
== BUILDMODE_LOW_ONLY
or (options
.buildmode
== BUILDMODE_AUTO
and options
.dimensions
== 2) then
834 vdata
[index
] = content_test_node_low
835 vdata2
[index
] = node_param2
837 vdata
[index
] = minetest
.CONTENT_AIR
846 stats
.avg
= sum_of_values
/ stats
.value_count
847 stats
.histogram_points
= cutoff_points
848 stats
.start_pos
= vector
.new(startpos
.x
, startpos
.y
, startpos
.z
)
849 stats
.end_pos
= vector
.add(stats
.start_pos
, vector
.new(options
.size
, options
.size
, options
.size
))
851 if not stats_mode
then
852 -- Write all the changes to map
853 vmanip
:set_data(vdata
)
854 vmanip
:set_param2_data(vdata2
)
855 vmanip
:write_to_map()
858 local time2
= minetest
.get_us_time()
859 local timediff
= time2
- time1
860 minetest
.log("verbose", "[perlin_explorer] Noisechunk calculated/generated in "..timediff
.." µs")
865 -- Initiates Perlin noise calculation and optionally node generation, too.
866 -- * pos: Where the Perlin noise starts
867 -- * noise: Perlin noise object
868 -- * noiseparams: noise parameters table
869 -- * options: table with:
870 -- * dimensions: number of Perlin noise dimensions (2 or 3)
871 -- * size: side length of area/volume to calculate)
872 -- * sidelen: pixelization
873 -- * stats_mode: if true, will only calculate values for statistics but not place any nodes
874 local create_perlin
= function(pos
, noise
, noiseparams
, options
, stats_mode
)
878 local local_options
= table.copy(options
)
879 local cpos
= table.copy(pos
)
880 local mpos
= vector
.round(cpos
)
882 local stats
= calculate_noise(mpos
, noise
, noiseparams
, local_options
, stats_mode
)
884 if mapgen_star
and not stats_mode
then
885 -- Show a particle in the center of the newly generated area
886 local center
= vector
.new()
887 center
.x
= mpos
.x
+ options
.size
/2
888 if options
.dimensions
== 2 then
889 center
.y
= mpos
.y
+ 3
891 center
.y
= mpos
.y
+ options
.size
/2
893 center
.z
= mpos
.z
+ options
.size
/2
894 minetest
.add_particle({
898 texture
= "perlin_explorer_new_noisechunk.png",
899 glow
= minetest
.LIGHT_MAX
,
904 minetest
.log("info", "[perlin_explorer] Perlin noise generated at %s! Stats: min. value=%.3f, max. value=%.3f, avg. value=%.3f", minetest
.pos_to_string(mpos
), stats
.min, stats
.max, stats
.avg
)
905 return S("Perlin noise generated at @1!", minetest
.pos_to_string(mpos
)), stats
907 minetest
.log("error", "[perlin_explorer] Could not get stats!")
912 -- Seeder tool helper function.
913 -- * player: Player object
914 -- * regen: If true, force map section to regenerate (if autogen is off)
915 local seeder_reseed
= function(player
, regen
)
917 if regen
and (not current_options
.autogen
and current_options
.pos
) then
918 msg
= S("New random seed set, starting to regenerate nodes …")
919 minetest
.chat_send_player(player
:get_player_name(), msg
)
920 msg
= create_perlin(current_options
.pos
, current_perlin
.noise
, current_perlin
.noiseparams
, current_options
, false)
922 minetest
.chat_send_player(player
:get_player_name(), msg
)
925 msg
= S("New random seed set!")
926 minetest
.chat_send_player(player
:get_player_name(), msg
)
930 -- Generates a function for the seeder tool callbacks, for the
931 -- on_use, on_secondary_use, on_place callbacks of that tool.
932 -- * reseed : If true, force map section to regenerate (if autogen is off)
933 local function seeder_use(reseed
)
934 return function(itemstack
, user
, pointed_thing
)
938 local privs
= minetest
.get_player_privs(user
:get_player_name())
939 if not privs
.server
then
940 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
943 if current_perlin
.noise
then
944 local noiseparams
= table.copy(current_perlin
.noiseparams
)
945 noiseparams
.seed
= math
.random(0, 2^
32-1)
946 set_perlin_noise(noiseparams
)
948 seeder_reseed(user
, reseed
)
950 local msg
= S("No active Perlin noise set. Set one first!")
951 minetest
.chat_send_player(user
:get_player_name(), msg
)
956 -- Converts a sidelen (=pixelize) value to a valid one
957 local fix_sidelen
= function(sidelen
)
958 return math
.max(1, math
.floor(sidelen
))
961 -- Fix some errors in the noiseparams
962 local fix_noiseparams
= function(noiseparams
)
963 noiseparams
.octaves
= math
.floor(math
.max(1, noiseparams
.octaves
))
964 noiseparams
.lacunarity
= math
.max(1.0, noiseparams
.lacunarity
)
965 noiseparams
.spread
.x
= math
.floor(math
.max(1, noiseparams
.spread
.x
))
966 noiseparams
.spread
.y
= math
.floor(math
.max(1, noiseparams
.spread
.y
))
967 noiseparams
.spread
.z
= math
.floor(math
.max(1, noiseparams
.spread
.z
))
971 -- Tool to set new random seed of the noiseparams
972 minetest
.register_tool("perlin_explorer:seeder", {
973 description
= S("Random Perlin seed setter"),
974 _tt_help
= S("Punch: Set a random seed for the active Perlin noise parameters").."\n"..
975 S("Place: Set a random seed and regenerate nodes (if applicable)"),
976 inventory_image
= "perlin_explorer_seeder.png",
977 wield_image
= "perlin_explorer_seeder.png",
978 groups
= { disable_repair
= 1 },
979 on_use
= seeder_use(false),
980 on_secondary_use
= seeder_use(true),
981 on_place
= seeder_use(true),
987 minetest
.register_chatcommand("perlin_set_options", {
988 privs
= { server
= true },
989 description
= S("Set Perlin map generation options"),
990 params
= S("<dimensions> <pixelize> <midpoint> <low_color> <high_color> [<node_type> <build_mode>]"),
991 func
= function(name
, param
)
992 local dimensions
, sidelen
, mid
, min, max, nodetype
, buildmode
= string.match(param
, "([23]) ([0-9]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9]) ([0-9])")
993 if not dimensions
then
994 dimensions
, sidelen
, mid
, min, max = string.match(param
, "([0-9]+) ([0-9]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+)")
995 if not dimensions
then
999 dimensions
= tonumber(dimensions
)
1000 sidelen
= tonumber(sidelen
)
1004 nodetype
= tonumber(nodetype
)
1005 buildmode
= tonumber(buildmode
)
1006 if not dimensions
or not sidelen
or not min or not mid
or not max then
1007 return false, S("Invalid parameter type! (only numbers are allowed)")
1009 dimensions
= math
.floor(dimensions
)
1010 if dimensions
~= 2 and dimensions
~= 3 then
1011 return false, S("Invalid dimensions! (must be 2 or 3)")
1014 -- Update current options
1015 if nodetype
and buildmode
then
1016 -- Check nodetype and buildmode
1017 nodetype
= math
.floor(nodetype
)
1018 buildmode
= math
.floor(buildmode
)
1019 if not nodetypes
[nodetype
] then
1020 return false, S("Invalid node type! (must be between 1 and @1)", #nodetypes
)
1022 if buildmode
< 1 or buildmode
> BUILDMODES_COUNT
then
1023 return false, S("Invalid build mode! (must be between 1 and @1)", BUILDMODES_COUNT
)
1025 current_options
.nodetype
= nodetype
1026 current_options
.buildmode
= buildmode
1028 current_options
.dimensions
= dimensions
1029 current_options
.sidelen
= fix_sidelen(sidelen
)
1030 current_options
.min_color
= min
1031 current_options
.mid_color
= mid
1032 current_options
.max_color
= max
1034 -- Force mapgen to update
1037 return true, S("Perlin map generation options set!")
1041 minetest
.register_chatcommand("perlin_set_noise", {
1042 privs
= { server
= true },
1043 description
= S("Set active Perlin noise parameters"),
1044 params
= S("<offset> <scale> <seed> <spread_x> <spread_y> <spread_z> <octaves> <persistence> <lacunarity> [<flags>]"),
1045 func
= function(name
, param
)
1046 local offset
, scale
, seed
, sx
, sy
, sz
, octaves
, persistence
, lacunarity
, flags
= string.match(param
, string.rep("([0-9.-]+) ", 9) .. "([a-zA-Z, ]+)")
1048 offset
, scale
, seed
, sx
, sy
, sz
, octaves
, persistence
, lacunarity
= string.match(param
, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)")
1056 octaves
= tonumber(octaves
)
1057 offset
= tonumber(offset
)
1061 persistence
= tonumber(persistence
)
1062 lacunarity
= tonumber(lacunarity
)
1063 seed
= tonumber(seed
)
1064 if not octaves
or not offset
or not sx
or not sy
or not sz
or not persistence
or not lacunarity
or not seed
then
1065 return false, S("Invalid parameter type.")
1067 local noiseparams
= {
1071 spread
= { x
= sx
, y
= sy
, z
= sz
},
1072 persistence
= persistence
,
1073 lacunarity
= lacunarity
,
1077 noiseparams
= fix_noiseparams(noiseparams
)
1078 local _
, _
, _
, badwaves
= analyze_noiseparams(noiseparams
)
1080 return false, TEXT_ERROR_BAD_WAVELENGTH
1082 set_perlin_noise(noiseparams
)
1084 return true, S("Active Perlin noise parameters set!")
1088 minetest
.register_chatcommand("perlin_generate", {
1089 privs
= { server
= true },
1090 description
= S("Generate active Perlin noise"),
1091 params
= S("<pos> <size>"),
1092 func
= function(name
, param
)
1093 local x
, y
, z
, size
= string.match(param
, "([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9]+)")
1100 size
= tonumber(size
)
1101 if not x
or not y
or not z
or not size
then
1104 if not x
or not y
or not z
or not size
then
1105 return false, S("Invalid parameter type.")
1107 local pos
= vector
.new(x
, y
, z
)
1109 minetest
.chat_send_player(name
, S("Creating Perlin noise, please wait …"))
1110 local opts
= table.copy(current_options
)
1112 local msg
= create_perlin(pos
, current_perlin
.noise
, current_perlin
.noiseparams
, opts
, false)
1113 if msg
== false then
1114 return false, S("No active Perlin noise set. Set one first!")
1120 minetest
.register_chatcommand("perlin_toggle_mapgen", {
1121 privs
= { server
= true },
1122 description
= S("Toggle the automatic Perlin noise map generator"),
1124 func
= function(name
, param
)
1125 if not current_perlin
.noise
then
1126 return false, S("No active Perlin noise set. Set one first!")
1128 current_options
.autogen
= not current_options
.autogen
1129 if current_options
.autogen
then
1132 minetest
.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options
.autogen
))
1134 if current_options
.autogen
then
1135 msg
= S("Mapgen enabled!")
1137 msg
= S("Mapgen disabled!")
1143 -- Show an error window to player with the given message.
1144 -- Set analyze_button to true to add a button "Analyze now"
1145 -- to open the analyze window.
1146 local show_error_formspec
= function(player
, message
, analyze_button
)
1148 if analyze_button
then
1149 buttons
= "button[1.5,2.5;3,0.75;done;"..F(S("Return")).."]"..
1150 "button[5.5,2.5;3,0.75;analyze;"..F(S("Analyze now"))
1152 buttons
= "button[3.5,2.5;3,0.75;done;"..F(S("Return")).."]"
1156 formspec_version[4]size[10,3.5]
1157 container[0.25,0.25]
1158 box[0,0;9.5,2;]]..FORMSPEC_BOX_COLOR
..[[]
1159 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1160 label[0.25,0.2;]]..F(S("Error"))..[[]
1162 textarea[0,0;9,1.4;;]]..F(message
)..[[;]
1166 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:error", form
)
1169 -- Stats loading screen
1170 local show_histogram_loading_formspec
= function(player
)
1172 formspec_version[4]size[11,2]
1173 container[0.25,0.25]
1174 box[0,0;10.5,1.5;]]..FORMSPEC_BOX_COLOR
..[[]
1175 box[0,0;10.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1176 label[0.25,0.2;]]..F(S("Statistical analysis in progress"))..[[]
1178 label[0,0;]]..F(S("Collecting data, please wait …"))..[[]
1182 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:histogram_loading", form
)
1186 local show_histogram_formspec
= function(player
, options
, stats
)
1190 histogram
= "vertlabel[0.1,0.1;"..F(S("Relative frequency")).."]"..
1191 "label[1.1,8.15;"..F(S("Noise values")).."]"..
1192 "label[0,7.0;"..F(S("Max.")).."\n"..F(S("Min.")).."]"..
1193 "tooltip[0,6.8;0.7,1;"..F(S("Upper bounds (exclusive) and lower bounds (inclusive) of the noise value data bucketing")).."]"..
1194 "box[0.75,0;0.025,8.5;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"..
1195 "box[0,6.65;"..HISTOGRAM_BUCKETS
..",0.025;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"..
1196 "box[0,7.8;"..HISTOGRAM_BUCKETS
..",0.025;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"
1198 -- Special case: If bucket sizes are equal, only show the last bucket
1199 -- (can happen if scale=0)
1200 if HISTOGRAM_BUCKETS
> 1 and stats
.histogram_points
[1] == stats
.histogram_points
[2] then
1201 hstart
= HISTOGRAM_BUCKETS
1204 for h
=hstart
, HISTOGRAM_BUCKETS
do
1205 local value
= stats
.histogram
[h
]
1206 if value
> max_value
then
1210 -- Drawn histogram bars, tooltips and labels
1211 for h
=hstart
, HISTOGRAM_BUCKETS
do
1212 local count
= stats
.histogram
[h
]
1213 local ratio
= (stats
.histogram
[h
] / stats
.value_count
)
1214 local bar_ratio
= (stats
.histogram
[h
] / max_value
)
1215 local perc
= ratio
* 100
1216 local perc_f
= string.format("%.1f", perc
)
1218 local bar_height
= math
.max(maxh
* bar_ratio
, 0.001)
1219 local coords
= x
..","..maxh
-bar_height
..";0.8,"..bar_height
1222 box
= box
.. "box["..coords
..";"..FORMSPEC_HISTOGRAM_BAR_COLOR
.."]"
1223 box
= box
.. "tooltip["..coords
..";"..count
.."]"
1225 box
= box
.. "label["..x
..",6.4;"..F(S("@1%", perc_f
)).."]"
1226 box
= box
.. "tooltip["..x
..",6.2;0.9,0.3;"..count
.."]"
1228 local min, max, min_v
, max_v
1232 min = F(string.format("%.1f", stats
.histogram_points
[h
-1]))
1234 if h
>= HISTOGRAM_BUCKETS
then
1237 max = F(string.format("%.1f", stats
.histogram_points
[h
]))
1240 box
= box
.. "label["..x
..",7.0;"..max.."\n"..min.."]"
1244 tt
= F(S("value < @1", stats
.histogram_points
[h
]))
1245 elseif h
== HISTOGRAM_BUCKETS
then
1246 tt
= F(S("@1 <= value", stats
.histogram_points
[h
-1]))
1248 tt
= F(S("@1 <= value < @2", stats
.histogram_points
[h
-1], stats
.histogram_points
[h
]))
1250 box
= box
.. "tooltip["..x
..",6.8;0.9,1;"..tt
.."]"
1252 histogram
= histogram
.. box
1255 if options
.dimensions
== 2 then
1256 vmin
= S("(@1,@2)", stats
.start_pos
.x
, stats
.start_pos
.z
)
1257 vmax
= S("(@1,@2)", stats
.end_pos
.x
, stats
.end_pos
.z
)
1259 vmin
= S("(@1,@2,@3)", stats
.start_pos
.x
, stats
.start_pos
.y
, stats
.start_pos
.z
)
1260 vmax
= S("(@1,@2,@3)", stats
.end_pos
.x
, stats
.end_pos
.y
, stats
.end_pos
.z
)
1262 local labels
= "textarea[0,0;"..HISTOGRAM_BUCKETS
..",2;;;"..
1263 F(S("Values were picked randomly between noise coordinates @1 and @2.", vmin
, vmax
)).."\n"..
1264 F(S("Values calculated: @1", stats
.value_count
)).."\n"..
1265 F(S("Minimum calculated value: @1", stats
.min)).."\n"..
1266 F(S("Maximum calculated value: @1", stats
.max)).."\n"..
1267 F(S("Average calculated value: @1", stats
.avg
)).."]\n"
1270 formspec_version[4]size[]]..(HISTOGRAM_BUCKETS
+1)..[[,12.5]
1271 container[0.25,0.25]
1272 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,2.5;]]..FORMSPEC_BOX_COLOR
..[[]
1273 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1274 label[0.25,0.2;]]..F(S("Noise Value Statistics"))..[[]
1279 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,9.25;]]..FORMSPEC_BOX_COLOR
..[[]
1280 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1281 label[0.25,0.2;]]..F(S("Noise Value Histogram"))..[[]
1286 button[7.25,11.35;3,0.5;done;]]..F(S("Done"))..[[]
1289 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:histogram", form
)
1292 -- Analyzes the given noise params and shows the result in a pretty-printed formspec to player
1293 local analyze_noiseparams_and_show_formspec
= function(player
, noiseparams
)
1294 local min, max, waves
= analyze_noiseparams(noiseparams
)
1295 local print_waves
= function(waves_a
)
1296 local stringified_waves
= {}
1297 for w
=1, #waves_a
do
1299 local is_bad
= false
1300 if minetest
.is_nan(waves_a
[w
]) or waves_a
[w
] == math
.huge
or waves_a
[w
] == -math
.huge
then
1301 strwave
= minetest
.colorize("#FF0000FF", waves_a
[w
])
1302 elseif waves_a
[w
] < 1 then
1303 strwave
= minetest
.colorize("#FF0000FF", "0")
1305 strwave
= string.format("%.0f", waves_a
[w
])
1307 table.insert(stringified_waves
, strwave
)
1309 return table.concat(stringified_waves
, ", ")
1312 formspec_version[4]size[10,5]
1313 container[0.25,0.25]
1314 box[0,0;9.5,3.5;]]..FORMSPEC_BOX_COLOR
..[[]
1315 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1316 label[0.25,0.2;]]..F(S("Noise Parameters Analysis"))..[[]
1318 label[0.25,0.75;]]..F(S("Minimum possible value: @1", min))..[[]
1319 label[0.25,1.25;]]..F(S("Maximum possible value: @1", max))..[[]
1321 label[0.25,1.75;]]..F(S("X wavelengths: @1", print_waves(waves
.x
)))..[[]
1322 label[0.25,2.25;]]..F(S("Y wavelengths: @1", print_waves(waves
.y
)))..[[]
1323 label[0.25,2.75;]]..F(S("Z wavelengths: @1", print_waves(waves
.z
)))..[[]
1325 button[3.5,3.75;3,0.75;done;]]..F(S("Done"))..[[]
1329 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:analyze", form
)
1332 -- Show the formspec of the Perlin Noise Creator tool to player.
1333 -- The main formspec of this mod!
1334 -- * player: Player object
1335 -- * noiseparams: Initialize formspec with these noiseparams (optional)
1336 -- * profile_id: Preselect this profile (by table index of np_profiles)
1337 -- * f_dimensions: Set to force dimensions (optional)
1338 -- * f_sidelen: Set to force sidelen (optional)
1339 local show_noise_formspec
= function(player
, noiseparams
, profile_id
, f_dimensions
, f_sidelen
)
1340 local player_name
= player
:get_player_name()
1345 np
= current_perlin
.noiseparams
1347 if not profile_id
then
1350 local offset
= tostring(np
.offset
or "")
1351 local scale
= tostring(np
.scale
or "")
1352 local seed
= tostring(np
.seed
or "")
1353 local sx
, sy
, sz
= "", "", ""
1355 sx
= tostring(np
.spread
.x
or "")
1356 sy
= tostring(np
.spread
.y
or "")
1357 sz
= tostring(np
.spread
.z
or "")
1359 local octaves
= tostring(np
.octaves
or "")
1360 local persistence
= tostring(np
.persistence
or "")
1361 local lacunarity
= tostring(np
.lacunarity
or "")
1363 local size
= tostring(current_options
.size
or "")
1364 local buildmode
= tostring(current_options
.buildmode
or 1)
1368 sidelen
= tostring(f_sidelen
)
1370 sidelen
= tostring(current_options
.sidelen
or "")
1372 local dimensions
= f_dimensions
1373 if not dimensions
then
1374 dimensions
= current_options
.dimensions
or 2
1376 local dimensions_index
= dimensions
- 1
1378 local pos_x
, pos_y
, pos_z
= "", "", ""
1379 if current_options
.pos
then
1380 pos_x
= tostring(current_options
.pos
.x
or "")
1381 pos_y
= tostring(current_options
.pos
.y
or "")
1382 pos_z
= tostring(current_options
.pos
.z
or "")
1384 local value_min
= tostring(current_options
.min_color
or "")
1385 local value_mid
= tostring(current_options
.mid_color
or "")
1386 local value_max
= tostring(current_options
.max_color
or "")
1388 local flags
= np
.flags
1389 local flags_table
= parse_flags_string(flags
)
1390 local flag_defaults
= tostring(flags_table
.defaults
)
1391 local flag_eased
= tostring(flags_table
.eased
)
1392 local flag_absvalue
= tostring(flags_table
.absvalue
)
1393 formspec_states
[player_name
].default
= flags_table
.defaults
== true
1394 formspec_states
[player_name
].absvalue
= flags_table
.absvalue
== true
1395 formspec_states
[player_name
].eased
= flags_table
.eased
== true
1397 local noiseparams_list
= {}
1399 for i
=1, #np_profiles
do
1400 local npp
= np_profiles
[i
]
1401 local name
= npp
.name
1402 -- Automatic naming for user profiles
1403 if npp
.np_type
== "user" then
1404 name
= S("Profile @1", counter
)
1405 counter
= counter
+ 1
1407 table.insert(noiseparams_list
, F(name
))
1409 local noiseparams_list_str
= table.concat(noiseparams_list
, ",")
1411 local nodetype_index
= (current_options
.nodetype
)
1413 local nodetypes_list
= {}
1414 for i
=1, #nodetypes
do
1415 table.insert(nodetypes_list
, F(nodetypes
[i
][1]))
1417 local nodetypes_list_str
= table.concat(nodetypes_list
, ",")
1419 local delete_btn
= ""
1420 if #np_profiles
> 1 then
1421 delete_btn
= "button[7.25,0;2.0,0.5;delete_np_profile;"..F(S("Delete")).."]"
1424 local create_btn
= ""
1426 if current_options
.autogen
then
1427 autogen_label
= S("Disable mapgen")
1429 autogen_label
= S("Enable mapgen")
1430 create_btn
= "button[3.50,0;3,1;create;"..F(S("Apply and create")).."]"..
1431 "tooltip[create;"..F(S("Set these noise parameters and noise options as the “active noise” and create nodes at the given X/Y/Z coordinates with the given size")).."]"
1433 field[0.25,1.95;1.75,0.75;pos_x;]]..F(S("X"))..[[;]]..pos_x
..[[]
1434 field[2.10,1.95;1.75,0.75;pos_y;]]..F(S("Y"))..[[;]]..pos_y
..[[]
1435 field[3.95,1.95;1.75,0.75;pos_z;]]..F(S("Z"))..[[;]]..pos_z
..[[]
1436 tooltip[pos_x;]]..F(S("Coordinate for “Apply and create”"))..[[]
1437 tooltip[pos_y;]]..F(S("Coordinate for “Apply and create”"))..[[]
1438 tooltip[pos_z;]]..F(S("Coordinate for “Apply and create”"))..[[]
1440 field[5.95,1.95;1.5,0.75;size;]]..F(S("Size"))..[[;]]..size
..[[]
1441 tooltip[size;]]..F(S("Size of area of nodes to generate (as a sidelength), used by “Apply and create”"))..[[]
1443 field_close_on_enter[pos_x;false]
1444 field_close_on_enter[pos_y;false]
1445 field_close_on_enter[pos_z;false]
1446 field_close_on_enter[size;false]
1450 formspec_version[4]size[10,12.5]
1451 container[0.25,0.25]
1452 box[0,0;9.5,5.5;]]..FORMSPEC_BOX_COLOR
..[[]
1453 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1454 label[0.15,0.2;]]..F(S("Noise parameters"))..[[]
1456 dropdown[0.25,0;3,0.5;np_profiles;]]..noiseparams_list_str
..[[;]]..profile_id
..[[;true]
1457 button[3.25,0;2.0,0.5;add_np_profile;]]..F(S("Add"))..[[]
1458 button[5.25,0;2.0,0.5;load_np_profile;]]..F(S("Load"))..[[]
1462 field[0.25,0;2,0.75;offset;]]..F(S("Offset"))..[[;]]..offset
..[[]
1463 field[3.25,0;2,0.75;scale;]]..F(S("Scale"))..[[;]]..scale
..[[]
1464 field[6.25,0;2,0.75;seed;]]..F(S("Seed"))..[[;]]..seed
..[[]
1465 image_button[8.35,0.0;0.75,0.75;perlin_explorer_seeder.png;set_random_seed;]
1466 field[0.25,1.2;2,0.75;spread_x;]]..F(S("X Spread"))..[[;]]..sx
..[[]
1467 field[3.25,1.2;2,0.75;spread_y;]]..F(S("Y Spread"))..[[;]]..sy
..[[]
1468 field[6.25,1.2;2,0.75;spread_z;]]..F(S("Z Spread"))..[[;]]..sz
..[[]
1469 field[0.25,2.4;2,0.75;octaves;]]..F(S("Octaves"))..[[;]]..octaves
..[[]
1470 field[3.25,2.4;2,0.75;persistence;]]..F(S("Persistence"))..[[;]]..persistence
..[[]
1471 field[6.25,2.4;2,0.75;lacunarity;]]..F(S("Lacunarity"))..[[;]]..lacunarity
..[[]
1472 checkbox[0.25,3.55;defaults;]]..F(S("defaults"))..[[;]]..flag_defaults
..[[]
1473 checkbox[2.25,3.55;eased;]]..F(S("eased"))..[[;]]..flag_eased
..[[]
1474 checkbox[4.25,3.55;absvalue;]]..F(S("absvalue"))..[[;]]..flag_absvalue
..[[]
1475 button[6.25,3.35;2.0,0.5;analyze;]]..F(S("Analyze"))..[[]
1478 field_close_on_enter[offset;false]
1479 field_close_on_enter[scale;false]
1480 field_close_on_enter[seed;false]
1481 field_close_on_enter[spread_x;false]
1482 field_close_on_enter[spread_y;false]
1483 field_close_on_enter[spread_z;false]
1484 field_close_on_enter[octaves;false]
1485 field_close_on_enter[persistence;false]
1486 field_close_on_enter[lacunarity;false]
1487 field_close_on_enter[sidelen;false]
1489 tooltip[set_random_seed;]]..F(S("Random seed"))..[[]
1490 tooltip[add_np_profile;]]..F(S("Add the current noise parameter as a new profile into the profile list"))..[[]
1491 tooltip[load_np_profile;]]..F(S("Load the selected profile from the profile list to replace the noise parameters"))..[[]
1492 tooltip[delete_np_profile;]]..F(S("Delete the selected profile from the profile list (only works for user profiles)"))..[[]
1493 tooltip[analyze;]]..F(S("Perform some basic mathematical analysis about these noise parameters"))..[[]
1498 box[0,0;9.5,1.6;]]..FORMSPEC_BOX_COLOR
..[[]
1499 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1500 label[0.15,0.2;]]..F(S("Noise options"))..[[]
1501 dropdown[0.25,0.7;1,0.75;dimensions;]]..F(S("2D"))..[[,]]..F(S("3D"))..[[;]]..dimensions_index
..[[;true]
1502 field[2.25,0.7;2,0.75;sidelen;]]..F(S("Pixelization"))..[[;]]..sidelen
..[[]
1503 button[6.25,0.7;2.0,0.6;statistics;]]..F(S("Statistics"))..[[]
1504 tooltip[statistics;]]..F(S("Calculate a large amount of noise values under the current settings and show a statistical analysis of these values"))..[[]
1505 tooltip[sidelen;]]..F(S("If higher than 1, Perlin values will be repeated along all axes every x nodes, for a ‘pixelized’ effect."))..[[]
1509 container[0.25,7.85]
1510 box[0,0;9.5,2.9;]]..FORMSPEC_BOX_COLOR
..[[]
1511 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1512 label[0.15,0.2;]]..F(S("Node generation"))..[[]
1513 field[0.25,0.75;1.75,0.75;value_mid;]]..F(S("Midpoint"))..[[;]]..value_mid
..[[]
1514 field[2.10,0.75;1.75,0.75;value_min;]]..F(S("Low color at"))..[[;]]..value_min
..[[]
1515 field[3.95,0.75;1.75,0.75;value_max;]]..F(S("High color at"))..[[;]]..value_max
..[[]
1516 button[5.95,0.75;1.00,0.75;autocolor;]]..F(S("Auto"))..[[]
1517 dropdown[7.55,0.75;1.9,0.75;nodetype;]]..nodetypes_list_str
..[[;]]..nodetype_index
..[[;true]
1518 tooltip[nodetype;]]..F(S("Node type: Specify the type of node that you want to generate here"))..[[]
1520 dropdown[7.55,1.95;1.9,0.75;buildmode;]]..F(S("Auto"))..","..F(S("All"))..","..F(S("High only"))..","..F(S("Low only"))..[[;]]..buildmode
..[[;true]
1521 tooltip[buildmode;]]..F(S("Build mode: Specify whether to place nodes for high or low noise values, or all values. Auto = “All” in 2D mode or “Low only” in 3D mode"))..[[]
1522 tooltip[value_mid;]]..F(S("Noise values equal to or higher than the midpoint are treated as high values, otherwise as low values. Used for node colorization."))..[[]
1523 tooltip[value_min;]]..F(S("The lowest noise value to be represented by the node color gradient."))..[[]
1524 tooltip[value_max;]]..F(S("The highest noise value to be represented by the node color gradient."))..[[]
1525 tooltip[autocolor;]]..F(S("Set the midpoint and low and high color values automatically according to the theoretical noise value boundaries."))..[[]
1526 field_close_on_enter[value_mid;false]
1527 field_close_on_enter[value_min;false]
1528 field_close_on_enter[value_max;false]
1533 button[0.25,0;3.15,1;apply;]]..F(S("Apply"))..[[]
1534 tooltip[apply;]]..F(S("Set these noise parameters and noise options as the “active noise”")).."]"..
1536 button[6.60,0;3.15,1;toggle_autogen;]]..F(autogen_label
)..[[]
1537 tooltip[toggle_autogen;]]..F(S("Toggle the automatic map generator"))..[[]
1540 -- Hack to fix Minetest issue (see function comment)
1541 form
= unique_formspec_spaces(player_name
, form
)
1542 minetest
.show_formspec(player_name
, "perlin_explorer:creator", form
)
1545 -- Formspec handling
1546 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
1547 -- Require 'server' priv
1548 local privs
= minetest
.get_player_privs(player
:get_player_name())
1549 if not privs
.server
then
1554 if formname
== "perlin_explorer:analyze" or formname
== "perlin_explorer:error" then
1556 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1557 show_noise_formspec(player
, noiseparams
)
1558 elseif fields
.analyze
then
1559 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1560 analyze_noiseparams_and_show_formspec(player
, noiseparams
)
1566 if formname
== "perlin_explorer:histogram" then
1568 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1569 local dimensions
= formspec_states
[player
:get_player_name()].dimensions
1570 local sidelen
= formspec_states
[player
:get_player_name()].sidelen
1571 show_noise_formspec(player
, noiseparams
, nil, dimensions
, sidelen
)
1577 if formname
~= "perlin_explorer:creator" then
1581 -- Handle checkboxes
1582 local player_name
= player
:get_player_name()
1583 local flags_touched
= false
1584 if fields
.defaults
== "true" then
1585 formspec_states
[player_name
].defaults
= true
1587 elseif fields
.defaults
== "false" then
1588 formspec_states
[player_name
].defaults
= false
1591 if fields
.eased
== "true" then
1592 formspec_states
[player_name
].eased
= true
1594 elseif fields
.eased
== "false" then
1595 formspec_states
[player_name
].eased
= false
1598 if fields
.absvalue
== "true" then
1599 formspec_states
[player_name
].absvalue
= true
1601 elseif fields
.absvalue
== "false" then
1602 formspec_states
[player_name
].absvalue
= false
1606 -- Deleting a profile does not require any other field
1607 if fields
.delete_np_profile
then
1608 if #np_profiles
<= 1 then
1611 local profile_to_delete
= tonumber(fields
.np_profiles
)
1612 -- Can only delete user profiles
1613 if np_profiles
[profile_to_delete
].np_type
~= "user" then
1616 table.remove(np_profiles
, profile_to_delete
)
1617 update_mod_stored_profiles()
1618 local new_id
= math
.max(1, profile_to_delete
- 1)
1619 show_noise_formspec(player
, default_noiseparams
, new_id
)
1622 -- Handle other fields
1623 local enter_pressed
= fields
.key_enter_field
~= nil
1624 local do_apply
= fields
.apply
~= nil or fields
.toggle_autogen
~= nil
1625 local do_create
= fields
.create
~= nil
1626 if current_options
.autogen
then
1627 do_apply
= do_apply
or enter_pressed
1629 do_create
= do_create
or enter_pressed
1631 local do_analyze
= fields
.analyze
~= nil
1632 if (do_create
or do_apply
or do_analyze
or fields
.add_np_profile
or fields
.np_profiles
) then
1633 if fields
.offset
and fields
.scale
and fields
.seed
and fields
.spread_x
and fields
.spread_y
and fields
.spread_z
and fields
.octaves
and fields
.persistence
and fields
.lacunarity
then
1635 local offset
= tonumber(fields
.offset
)
1636 local scale
= tonumber(fields
.scale
)
1637 local seed
= tonumber(fields
.seed
)
1638 local sx
= tonumber(fields
.spread_x
)
1639 local sy
= tonumber(fields
.spread_y
)
1640 local sz
= tonumber(fields
.spread_z
)
1641 if not sx
or not sy
or not sz
then
1644 local spread
= vector
.new(sx
, sy
, sz
)
1645 local octaves
= tonumber(fields
.octaves
)
1646 local persistence
= tonumber(fields
.persistence
)
1647 local lacunarity
= tonumber(fields
.lacunarity
)
1648 local dimensions
= tonumber(fields
.dimensions
)
1649 local sidelen
= tonumber(fields
.sidelen
)
1650 local px
= tonumber(fields
.pos_x
)
1651 local py
= tonumber(fields
.pos_y
)
1652 local pz
= tonumber(fields
.pos_z
)
1653 local size
= tonumber(fields
.size
)
1654 local value_min
= tonumber(fields
.value_min
)
1655 local value_mid
= tonumber(fields
.value_mid
)
1656 local value_max
= tonumber(fields
.value_max
)
1657 local nodetype
= tonumber(fields
.nodetype
)
1658 local buildmode
= tonumber(fields
.buildmode
)
1659 if (offset
and scale
and spread
and octaves
and persistence
) then
1660 local defaults
= formspec_states
[player_name
].defaults
1661 local eased
= formspec_states
[player_name
].eased
1662 local absvalue
= formspec_states
[player_name
].absvalue
1663 local noiseparams
= {
1669 persistence
= persistence
,
1670 lacunarity
= lacunarity
,
1671 flags
= build_flags_string(defaults
, eased
, absvalue
),
1673 noiseparams
= fix_noiseparams(noiseparams
)
1674 -- Open analyze window
1676 formspec_states
[player_name
].noiseparams
= noiseparams
1677 analyze_noiseparams_and_show_formspec(player
, noiseparams
)
1679 -- Change NP profile selection
1680 elseif fields
.load_np_profile
and fields
.np_profiles
then
1681 local profile
= tonumber(fields
.np_profiles
)
1683 if np_profiles
[profile
].np_type
== "active" then
1684 -- The "active" profile is a special profile
1685 -- that reads from the active noiseparams.
1686 loaded_np
= current_perlin
.noiseparams
1688 -- Normal case: Read noiseparams from profile itself.
1689 loaded_np
= np_profiles
[profile
].noiseparams
1692 show_noise_formspec(player
, loaded_np
, profile
)
1693 minetest
.log("action", "[perlin_explorer] Loaded perlin noise profile "..profile
)
1695 -- Add new profile and save current noiseparams to it
1696 elseif fields
.add_np_profile
then
1697 table.insert(np_profiles
, {
1699 noiseparams
= noiseparams
,
1701 update_mod_stored_profiles()
1702 local new_profile
= #np_profiles
1703 minetest
.log("action", "[perlin_explorer] Perlin noise profile "..new_profile
.." added!")
1704 show_noise_formspec(player
, noiseparams
, new_profile
)
1706 elseif fields
.set_random_seed
then
1708 local profile
= tonumber(fields
.np_profiles
)
1709 noiseparams
.seed
= math
.random(0, 2^
32-1)
1710 show_noise_formspec(player
, noiseparams
, profile
)
1712 elseif fields
.autocolor
then
1713 -- Set color fields automatically
1714 local min, max = analyze_noiseparams(noiseparams
)
1715 current_options
.min_color
= min
1716 current_options
.max_color
= max
1717 current_options
.mid_color
= min + ((max - min) / 2)
1718 local profile
= tonumber(fields
.np_profiles
)
1719 show_noise_formspec(player
, noiseparams
, profile
)
1723 if not (dimensions
and sidelen
and value_min
and value_mid
and value_max
and nodetype
and buildmode
) then
1726 -- Convert dropdown index to actual dimensions number
1727 dimensions
= dimensions
+ 1
1728 -- Spread is used differently in 2D
1729 if dimensions
== 2 then
1732 local _
, _
, _
, badwaves
= analyze_noiseparams(noiseparams
)
1734 formspec_states
[player_name
].noiseparams
= noiseparams
1735 show_error_formspec(player
, TEXT_ERROR_BAD_WAVELENGTH
, true)
1739 -- Start statistics calculation
1740 if fields
.statistics
then
1741 formspec_states
[player_name
].noiseparams
= noiseparams
1742 formspec_states
[player_name
].dimensions
= dimensions
1743 formspec_states
[player_name
].sidelen
= sidelen
1744 local max_spread
= 0
1745 max_spread
= math
.max(max_spread
, noiseparams
.spread
.x
)
1746 max_spread
= math
.max(max_spread
, noiseparams
.spread
.y
)
1747 max_spread
= math
.max(max_spread
, noiseparams
.spread
.z
)
1748 local ssize
= max_spread
* STATISTICS_SPREAD_FACTOR
1749 -- A very large size is not allowed because the Minetest functions start to fail and would start to distort the statistics.
1750 if ssize
> STATISTICS_MAX_SIZE
or max_spread
> STATISTICS_MAX_SPREAD
then
1751 show_error_formspec(player
, S("Sorry, but Perlin Explorer doesn’t support calculating statistics if the spread of any axis is larger than @1.", STATISTICS_MAX_SPREAD
), false)
1754 -- Enforce a minimum size as well to at least 1 million values are in the area
1755 if dimensions
== 2 and ssize
< 1000 then
1757 elseif dimensions
== 3 and ssize
< 100 then
1760 local start
= -math
.floor(ssize
/2)
1762 local pos
= vector
.new(start
, start
, start
)
1763 local noise
= PerlinNoise(noiseparams
)
1765 dimensions
= dimensions
,
1769 -- Show a loading formspec
1770 show_histogram_loading_formspec(player
)
1772 local _
, stats
= create_perlin(pos
, noise
, noiseparams
, options
, true)
1774 -- Update the formspec to show the result
1775 show_histogram_formspec(player
, options
, stats
)
1777 minetest
.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
1783 set_perlin_noise(noiseparams
)
1784 minetest
.log("action", "[perlin_explorer] Perlin noise set!")
1785 current_options
.dimensions
= dimensions
1786 current_options
.sidelen
= fix_sidelen(sidelen
)
1787 current_options
.min_color
= value_min
1788 current_options
.mid_color
= value_mid
1789 current_options
.max_color
= value_max
1790 current_options
.nodetype
= nodetype
1791 current_options
.buildmode
= buildmode
1792 if fields
.toggle_autogen
then
1793 current_options
.autogen
= not current_options
.autogen
1794 if current_options
.autogen
then
1797 local profile
= tonumber(fields
.np_profiles
)
1798 show_noise_formspec(player
, noiseparams
, profile
)
1799 minetest
.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options
.autogen
))
1800 elseif do_create
then
1801 if not px
or not py
or not pz
or not size
then
1804 if current_options
.autogen
then
1807 current_options
.size
= size
1808 local place_pos
= vector
.new(px
, py
, pz
)
1809 current_options
.pos
= place_pos
1810 local msg
= S("Creating Perlin noise, please wait …")
1811 minetest
.chat_send_player(player_name
, msg
)
1812 msg
= create_perlin(place_pos
, current_perlin
.noise
, current_perlin
.noiseparams
, current_options
, false)
1814 minetest
.chat_send_player(player_name
, msg
)
1815 elseif msg
== false then
1816 minetest
.log("error", "[perlin_explorer] Error generating Perlin noise nodes!")
1818 elseif do_apply
and current_options
.autogen
then
1820 elseif do_apply
and not current_options
.autogen
then
1821 if not px
or not py
or not pz
then
1824 local place_pos
= vector
.new(px
, py
, pz
)
1825 current_options
.pos
= place_pos
1832 -- The main tool of this mod. Opens the Perlin noise creation window.
1833 minetest
.register_tool("perlin_explorer:creator", {
1834 description
= S("Perlin Noise Creator"),
1835 _tt_help
= S("Punch to open the Perlin noise creation menu"),
1836 inventory_image
= "perlin_explorer_creator.png",
1837 wield_image
= "perlin_explorer_creator.png",
1838 groups
= { disable_repair
= 1 },
1839 on_use
= function(itemstack
, user
, pointed_thing
)
1843 local privs
= minetest
.get_player_privs(user
:get_player_name())
1844 if not privs
.server
then
1845 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
1848 show_noise_formspec(user
)
1852 -- Automatic map generation (autogen).
1853 -- The autogen is kind of a mapgen, but it doesn't use Minetest's
1854 -- mapgen, instead it writes directly to map. The benefit is
1855 -- that this will instantly update when the active noiseparams
1857 -- This will generate so-called noisechunks, which are like
1858 -- Minetest mapchunks, except with respect to this mod only.
1860 minetest
.register_globalstep(function(dtime
)
1861 timer
= timer
+ dtime
1862 if timer
< AUTOBUILD_UPDATE_TIME
then
1866 if current_perlin
.noise
and current_options
.autogen
then
1867 local players
= minetest
.get_connected_players()
1868 -- Generate close to all connected players
1869 for p
=1, #players
do
1870 local player
= players
[p
]
1871 local player_name
= player
:get_player_name()
1872 -- Helper function for building
1873 local build
= function(pos
, pos_hash
, player_name
)
1874 if not pos
or not pos
.x
or not pos
.y
or not pos
.z
then
1875 minetest
.log("error", "[perlin_explorer] build(): Invalid pos!")
1878 if not pos_hash
then
1879 minetest
.log("error", "[perlin_explorer] build(): Invalid pos_hash!")
1882 if not loaded_areas
[pos_hash
] then
1883 local opts
= table.copy(current_options
)
1884 opts
.size
= AUTOBUILD_SIZE
1885 -- This actually builds the nodes
1886 create_perlin(pos
, current_perlin
.noise
, current_perlin
.noiseparams
, opts
, false)
1887 -- Built chunks are marked so they don't get generated over and over
1889 loaded_areas
[pos_hash
] = true
1893 -- Build list of coordinates to generate nodes in
1894 local pos
= vector
.round(player
:get_pos())
1895 pos
= sidelen_pos(pos
, AUTOBUILD_SIZE
)
1896 local neighbors
= { vector
.new(0, 0, 0) }
1897 local c
= AUTOBUILD_CHUNKDIST
1899 if current_options
.dimensions
== 2 then
1905 table.insert(neighbors
, vector
.new(cx
, cy
, cz
))
1909 local noisechunks
= {}
1910 for n
=1, #neighbors
do
1911 local offset
= vector
.multiply(neighbors
[n
], AUTOBUILD_SIZE
)
1912 local npos
= vector
.add(pos
, offset
)
1913 -- Check of position is still within the valid map
1914 local node
= minetest
.get_node_or_nil(npos
)
1916 local hash
= minetest
.hash_node_position(npos
)
1917 if not loaded_areas
[hash
] then
1918 table.insert(noisechunks
, {npos
, hash
})
1922 if #noisechunks
> 0 then
1923 minetest
.log("verbose", "[perlin_explorer] Started building "..#noisechunks
.." noisechunk(s)")
1926 for c
=1, #noisechunks
do
1927 local npos
= noisechunks
[c
][1]
1928 local nhash
= noisechunks
[c
][2]
1929 build(npos
, nhash
, player_name
)
1931 if #noisechunks
> 0 then
1932 minetest
.log("verbose", "[perlin_explorer] Done building "..#noisechunks
.." noisechunk(s)")
1938 -- Player variable init and cleanup
1939 minetest
.register_on_joinplayer(function(player
)
1940 local name
= player
:get_player_name()
1941 local flags
= default_noiseparams
.flags
1942 local flags_table
= parse_flags_string(flags
)
1943 formspec_states
[name
] = {
1944 defaults
= flags_table
.defaults
,
1945 eased
= flags_table
.eased
,
1946 absvalue
= flags_table
.absvalue
,
1949 noiseparams
= table.copy(default_noiseparams
),
1951 -- This is used by the `unique_formspec_spaces` hack
1952 sequence_number
= 0,
1955 minetest
.register_on_leaveplayer(function(player
)
1956 local name
= player
:get_player_name()
1957 formspec_states
[name
] = nil