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 -- Calculate the two possible extreme values
350 -- with the octave value being either at 1 or -1.
351 local limit1
= (1 * np
.persistence ^
exp)
353 if not is_absolute
then
354 limit2
= (-1 * np
.persistence ^
exp)
356 -- If absvalue is set, one of the
357 -- limits is always 0 because we
362 -- To add to the maximum, pick the higher value
363 if limit1
> limit2
then
364 o_max
= o_max
+ limit1
366 o_max
= o_max
+ limit2
369 -- To add to the minimum, pick the LOWER value
370 if limit1
> limit2
then
371 o_min
= o_min
+ limit2
373 o_min
= o_min
+ limit1
376 -- Add offset and scale to min/max value (final step)
377 local min_value
= np
.offset
+ np
.scale
* o_min
378 local max_value
= np
.offset
+ np
.scale
* o_max
380 -- Bring the 2 values in the correct order
381 -- (min_value might be bigger for negative scale)
382 if min_value
> max_value
then
383 min_value
, max_value
= max_value
, min_value
386 local bad_wavelength
= false
387 -- Calculate "wavelengths"
388 local axes
= { "x", "y", "z" }
393 local wave
= np
.spread
[w
]
394 for o
=1, np
.octaves
do
396 bad_wavelength
= true
398 table.insert(waves
[w
], wave
)
399 wave
= wave
* (1 / np
.lacunarity
)
402 return min_value
, max_value
, waves
, bad_wavelength
405 -- Add stone node to nodetypes, if present
406 minetest
.register_on_mods_loaded(function()
407 local stone
= minetest
.registered_aliases
["mapgen_stone"]
409 local desc
= minetest
.registered_nodes
[stone
].description
413 table.insert(nodetypes
, { desc
, stone
, "air", false})
421 -- Add a bunch of nodes to generate a map based on a perlin noise.
422 -- Used for visualization.
423 -- Each nodes comes in a "high" and "low" variant.
424 -- high/low means it represents high/low noise values.
425 local paramtype2
, palette
426 if COLORIZE_NODES
then
429 if grayscale_colors
then
430 palette
= "perlin_explorer_node_palette_gray.png"
431 palette_low
= "perlin_explorer_node_palette_gray_low.png"
433 palette
= "perlin_explorer_node_palette.png"
434 palette_low
= "perlin_explorer_node_palette_low.png"
437 -- Solid nodes: Visible, walkable, opaque
438 minetest
.register_node("perlin_explorer:node", {
439 description
= S("Solid Perlin Test Node (High Value)"),
441 -- Intentionally does not cast shadow so that
442 -- cave structures are always fullbright when
444 sunlight_propagates
= true,
445 paramtype2
= paramtype2
,
446 tiles
= {"perlin_explorer_node.png"},
448 groups
= { dig_immediate
= 3 },
449 -- Force-drop without metadata to avoid spamming the inventory
450 drop
= "perlin_explorer:node",
452 minetest
.register_node("perlin_explorer:node_low", {
453 description
= S("Solid Perlin Test Node (Low Value)"),
455 sunlight_propagates
= true,
456 paramtype2
= paramtype2
,
457 tiles
= {"perlin_explorer_node_low.png"},
458 palette
= palette_low
,
459 groups
= { dig_immediate
= 3 },
460 -- Force-drop without metadata to avoid spamming the inventory
461 drop
= "perlin_explorer:node_low",
464 -- Grid nodes: See-through, walkable. Looks like glass.
465 -- Useful to see "inside" of 3D blobs.
466 minetest
.register_node("perlin_explorer:grid", {
467 description
= S("Grid Perlin Test Node (High Value)"),
469 drawtype
= "allfaces",
470 use_texture_alpha
= "clip",
471 sunlight_propagates
= true,
472 paramtype2
= paramtype2
,
473 tiles
= {"perlin_explorer_grid.png"},
474 palette
= "perlin_explorer_node_palette.png",
476 groups
= { dig_immediate
= 3 },
477 drop
= "perlin_explorer:grid",
479 minetest
.register_node("perlin_explorer:grid_low", {
480 description
= S("Grid Perlin Test Node (Low Value)"),
482 drawtype
= "allfaces",
483 sunlight_propagates
= true,
484 paramtype2
= paramtype2
,
485 tiles
= {"perlin_explorer_grid_low.png"},
486 use_texture_alpha
= "clip",
487 palette
= palette_low
,
488 groups
= { dig_immediate
= 3 },
489 drop
= "perlin_explorer:grid_low",
492 -- Minibox nodes: See-through, non-walkable, climbable.
493 -- Looks like dot clouds in large numbers.
494 -- Useful to see and move "inside" of 3D blobs.
495 minetest
.register_node("perlin_explorer:mini", {
496 description
= S("Minibox Perlin Test Node (High Value)"),
498 drawtype
= "nodebox",
503 fixed
= { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
505 use_texture_alpha
= "clip",
506 sunlight_propagates
= true,
507 paramtype2
= paramtype2
,
508 tiles
= {"perlin_explorer_mini.png"},
510 groups
= { dig_immediate
= 3 },
511 drop
= "perlin_explorer:mini",
513 minetest
.register_node("perlin_explorer:mini_low", {
514 description
= S("Minibox Perlin Test Node (Low Value)"),
516 drawtype
= "nodebox",
521 fixed
= { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
523 sunlight_propagates
= true,
524 paramtype2
= paramtype2
,
525 tiles
= {"perlin_explorer_mini_low.png"},
526 use_texture_alpha
= "clip",
527 palette
= palette_low
,
528 groups
= { dig_immediate
= 3 },
529 drop
= "perlin_explorer:mini_low",
532 -- Helper function for the getter tool. Gets a noise value at pos
533 -- and print is in user's chat.
534 -- * pos: Position to get
535 -- * user: Player object
536 -- * precision: Coordinate precision of output (for minetest.pos_to_string)
537 -- * ptype: One of ...
538 -- "node": Get value at node position
539 -- "player": Get value at player position
540 local print_value
= function(pos
, user
, precision
, ptype
)
542 local getpos
= sidelen_pos(pos
, current_options
.sidelen
)
543 if current_options
.dimensions
== 2 then
544 val
= current_perlin
.noise
:get_2d({x
=getpos
.x
, y
=getpos
.z
})
545 elseif current_options
.dimensions
== 3 then
546 val
= current_perlin
.noise
:get_3d(getpos
)
548 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
552 local color_node
= minetest
.get_color_escape_sequence("#FFD47CFF")
553 local color_player
= minetest
.get_color_escape_sequence("#87FF87FF")
554 local color_end
= minetest
.get_color_escape_sequence("#FFFFFFFF")
555 if ptype
== "node" then
556 msg
= S("Value at @1node@2 pos @3: @4", color_node
, color_end
, minetest
.pos_to_string(pos
, precision
), val
)
557 elseif ptype
== "player" then
558 msg
= S("Value at @1player@2 pos @3: @4", color_player
, color_end
, minetest
.pos_to_string(pos
, precision
), val
)
560 error("[perlin_explorer] Invalid ptype in print_value()!")
562 minetest
.chat_send_player(user
:get_player_name(), msg
)
565 -- Get Perlin value of player pos (on_use callback)
566 local use_getter
= function(itemstack
, user
, pointed_thing
)
570 local privs
= minetest
.get_player_privs(user
:get_player_name())
571 if not privs
.server
then
572 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
575 if current_perlin
.noise
then
576 local pos
= user
:get_pos()
577 local ctrl
= user
:get_player_control()
579 if not ctrl
.sneak
then
580 pos
= vector
.round(pos
)
583 print_value(pos
, user
, precision
, "player")
585 local msg
= S("No active Perlin noise set. Set one first!")
586 minetest
.chat_send_player(user
:get_player_name(), msg
)
590 -- Get Perlin value of pointed node (on_place callback)
591 local place_getter
= function(itemstack
, user
, pointed_thing
)
595 local privs
= minetest
.get_player_privs(user
:get_player_name())
596 if not privs
.server
then
597 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
600 if current_perlin
.noise
then
601 if pointed_thing
.type ~= "node" then
602 -- No-op for non-nodes
605 local pos
= pointed_thing
.under
606 print_value(pos
, user
, 0, "node")
608 local msg
= S("No active Perlin noise set. Set one first!")
609 minetest
.chat_send_player(user
:get_player_name(), msg
)
613 -- Gets perlin noise value
614 minetest
.register_tool("perlin_explorer:getter", {
615 description
= S("Perlin Value Getter"),
616 _tt_help
= S("Place: Display Perlin noise value of the pointed node position").."\n"..
617 S("Punch: Display Perlin noise value of player position (+Sneak: precise position)"),
618 inventory_image
= "perlin_explorer_getter.png",
619 wield_image
= "perlin_explorer_getter.png",
620 groups
= { disable_repair
= 1 },
622 on_place
= place_getter
,
625 --[[ Calculate the Perlin noise value and optionally generate nodes.
626 * pos: Bottom front left position of where Perlin noise begins
627 * noise: Perlin noise object to use
628 * noiseparams: noise parameters table
629 * options: see at create_perlin function
630 * stats_mode: if true, will only calculate values for statistics but not place any nodes
631 Returns: a stats table of the form
633 min, -- minimum calculated noise value
634 max, -- maximum calculated noise value
635 avg, -- average calculated noise value
636 value_count, -- number of values that were calculated
637 histogram, -- histogram data for the stats screen. A list
638 -- of "buckets", starting with the lower bounds:
640 [1] = <number of values in 1st bucket>,
641 [2] = <number of values in 2nd bucket>,
643 [HISTOGRAM_BUCKETS] = <number of values in last bucket>,
645 histogram_points, -- List of cutoff points for each of the
647 start_pos, -- lower corner of the area that the stats were calculated for
648 end_pos, -- upper corner of the area that the stats were calculated for
650 Returns nil on error.
652 local calculate_noise
= function(pos
, noise
, noiseparams
, options
, stats_mode
)
658 local time1
= minetest
.get_us_time()
667 local endpos
= vector
.add(startpos
, options
.size
-1)
669 local y_max
= endpos
.y
- startpos
.y
670 if options
.dimensions
== 2 then
671 -- We don't need 3rd axis in 2D
678 local vmanip
, emin
, emax
, vdata
, vdata2
, varea
679 local content_test_node
, content_test_node_low
, node_needs_color
680 if not stats_mode
then
681 -- Only needed when we want to place nodes
682 vmanip
= VoxelManip(startpos
, endpos
)
683 emin
, emax
= vmanip
:get_emerged_area()
684 vdata
= vmanip
:get_data()
685 vdata2
= vmanip
:get_param2_data()
686 varea
= VoxelArea
:new({MinEdge
= emin
, MaxEdge
= emax
})
688 content_test_node
= minetest
.get_content_id(nodetypes
[options
.nodetype
][2])
689 content_test_node_low
= minetest
.get_content_id(nodetypes
[options
.nodetype
][3])
690 node_needs_color
= nodetypes
[options
.nodetype
][4]
696 local sum_of_values
= 0
697 stats
.value_count
= 0
700 local min_possible
, max_possible
= analyze_noiseparams(noiseparams
)
701 local cutoff_points
= {}
702 -- Calculate the cutoff points for the histogram so we know in which data bucket
703 -- to put each value into.
704 for d
=1,HISTOGRAM_BUCKETS
do
705 cutoff_points
[d
] = min_possible
+ ((max_possible
-min_possible
) / HISTOGRAM_BUCKETS
) * d
706 stats
.histogram
[d
] = 0
710 if not stats_mode
then
711 -- Initialize Perlin map
712 -- The noise values will come from this (unless in Stats Mode)
713 local perlin_map_object
= PerlinNoiseMap(noiseparams
, size_v
)
714 if options
.dimensions
== 2 then
715 perlin_map
= perlin_map_object
:get_2d_map({x
=startpos
.x
, y
=startpos
.z
})
717 perlin_map
= perlin_map_object
:get_3d_map(startpos
)
723 x_max
= STATISTICS_ITERATIONS
- 1
727 x_max
= endpos
.x
- startpos
.x
728 z_max
= endpos
.z
- startpos
.z
731 -- Main loop (time-critical!)
735 -- Note: This loop has been optimized for speed, so the code
736 -- might not look pretty.
737 -- Be careful of the performance implications when touching this
740 -- Get Perlin value at current pos
742 if not stats_mode
then
748 elseif stats_mode
then
750 x
= math
.random(startpos
.x
, startpos
.x
+options
.size
),
751 y
= math
.random(startpos
.y
, startpos
.y
+options
.size
),
752 z
= math
.random(startpos
.z
, startpos
.z
+options
.size
),
755 -- Apply sidelen transformation (pixelize)
756 local abspos_get
= sidelen_pos(abspos
, options
.sidelen
)
758 x
= abspos_get
.x
- startpos
.x
+ 1,
759 y
= abspos_get
.y
- startpos
.y
+ 1,
760 z
= abspos_get
.z
- startpos
.z
+ 1,
763 -- Finally get the noise value
765 if options
.dimensions
== 2 then
766 if stats_mode
or (indexpos
.x
< 1 or indexpos
.z
< 1) then
767 -- The pixelization can move indexpos below 1, in this case
768 -- we get the perlin value directly because it is outside
769 -- the precalculated map. Performance impact is hopefully
770 -- not too bad because this will only occur at the low
772 -- Ideally, for cleaner code, the precalculated map would
773 -- take this into account as well but this has not
775 -- In stats mode, we always calculate the noise value directy
776 -- because the values are spread out.
777 perlin_value
= noise
:get_2d({x
=abspos_get
.x
, y
=abspos_get
.z
})
779 -- Normal case: Get value from perlin map
780 perlin_value
= perlin_map
[indexpos
.z
][indexpos
.x
]
782 elseif options
.dimensions
== 3 then
783 if stats_mode
or (indexpos
.x
< 1 or indexpos
.y
< 1 or indexpos
.z
< 1) then
785 perlin_value
= noise
:get_3d(abspos_get
)
788 perlin_value
= perlin_map
[indexpos
.z
][indexpos
.y
][indexpos
.x
]
791 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
796 if not stats
.min then
797 stats
.min = perlin_value
798 elseif perlin_value
< stats
.min then
799 stats
.min = perlin_value
801 if not stats
.max then
802 stats
.max = perlin_value
803 elseif perlin_value
> stats
.max then
804 stats
.max = perlin_value
807 for c
=1, HISTOGRAM_BUCKETS
do
808 if perlin_value
< cutoff_points
[c
] or c
>= HISTOGRAM_BUCKETS
then
809 stats
.histogram
[c
] = stats
.histogram
[c
] + 1
814 sum_of_values
= sum_of_values
+ perlin_value
815 stats
.value_count
= stats
.value_count
+ 1
817 -- This section will set the node
818 if not stats_mode
then
819 -- Calculate color (param2) for node
820 local zeropoint
= options
.mid_color
821 local min_size
= zeropoint
- options
.min_color
822 local max_size
= options
.max_color
- zeropoint
823 local node_param2
= 0
824 if node_needs_color
then
825 if perlin_value
>= zeropoint
then
826 node_param2
= ((perlin_value
- zeropoint
) / max_size
) * 255
828 node_param2
= ((zeropoint
- perlin_value
) / min_size
) * 255
830 node_param2
= math
.floor(math
.abs(node_param2
))
831 node_param2
= math
.max(0, math
.min(255, node_param2
))
832 if node_param2
< 255 then
833 node_param2
= node_param2
- (node_param2
% color_precision
)
838 local index
= varea
:indexp(abspos
)
843 -- Set node and param2 in vmanip
844 if perlin_value
>= zeropoint
then
845 if options
.buildmode
== BUILDMODE_ALL
or options
.buildmode
== BUILDMODE_HIGH_ONLY
or options
.buildmode
== BUILDMODE_AUTO
then
846 vdata
[index
] = content_test_node
847 vdata2
[index
] = node_param2
849 vdata
[index
] = minetest
.CONTENT_AIR
853 if options
.buildmode
== BUILDMODE_ALL
or options
.buildmode
== BUILDMODE_LOW_ONLY
or (options
.buildmode
== BUILDMODE_AUTO
and options
.dimensions
== 2) then
854 vdata
[index
] = content_test_node_low
855 vdata2
[index
] = node_param2
857 vdata
[index
] = minetest
.CONTENT_AIR
866 stats
.avg
= sum_of_values
/ stats
.value_count
867 stats
.histogram_points
= cutoff_points
868 stats
.start_pos
= vector
.new(startpos
.x
, startpos
.y
, startpos
.z
)
869 stats
.end_pos
= vector
.add(stats
.start_pos
, vector
.new(options
.size
, options
.size
, options
.size
))
871 if not stats_mode
then
872 -- Write all the changes to map
873 vmanip
:set_data(vdata
)
874 vmanip
:set_param2_data(vdata2
)
875 vmanip
:write_to_map()
878 local time2
= minetest
.get_us_time()
879 local timediff
= time2
- time1
880 minetest
.log("verbose", "[perlin_explorer] Noisechunk calculated/generated in "..timediff
.." µs")
885 -- Initiates Perlin noise calculation and optionally node generation, too.
886 -- * pos: Where the Perlin noise starts
887 -- * noise: Perlin noise object
888 -- * noiseparams: noise parameters table
889 -- * options: table with:
890 -- * dimensions: number of Perlin noise dimensions (2 or 3)
891 -- * size: side length of area/volume to calculate)
892 -- * sidelen: pixelization
893 -- * stats_mode: if true, will only calculate values for statistics but not place any nodes
894 local create_perlin
= function(pos
, noise
, noiseparams
, options
, stats_mode
)
898 local local_options
= table.copy(options
)
899 local cpos
= table.copy(pos
)
900 local mpos
= vector
.round(cpos
)
902 local stats
= calculate_noise(mpos
, noise
, noiseparams
, local_options
, stats_mode
)
904 if mapgen_star
and not stats_mode
then
905 -- Show a particle in the center of the newly generated area
906 local center
= vector
.new()
907 center
.x
= mpos
.x
+ options
.size
/2
908 if options
.dimensions
== 2 then
909 center
.y
= mpos
.y
+ 3
911 center
.y
= mpos
.y
+ options
.size
/2
913 center
.z
= mpos
.z
+ options
.size
/2
914 minetest
.add_particle({
918 texture
= "perlin_explorer_new_noisechunk.png",
919 glow
= minetest
.LIGHT_MAX
,
924 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
)
925 return S("Perlin noise generated at @1!", minetest
.pos_to_string(mpos
)), stats
927 minetest
.log("error", "[perlin_explorer] Could not get stats!")
932 -- Seeder tool helper function.
933 -- * player: Player object
934 -- * regen: If true, force map section to regenerate (if autogen is off)
935 local seeder_reseed
= function(player
, regen
)
937 if regen
and (not current_options
.autogen
and current_options
.pos
) then
938 msg
= S("New random seed set, starting to regenerate nodes …")
939 minetest
.chat_send_player(player
:get_player_name(), msg
)
940 msg
= create_perlin(current_options
.pos
, current_perlin
.noise
, current_perlin
.noiseparams
, current_options
, false)
942 minetest
.chat_send_player(player
:get_player_name(), msg
)
945 msg
= S("New random seed set!")
946 minetest
.chat_send_player(player
:get_player_name(), msg
)
950 -- Generates a function for the seeder tool callbacks, for the
951 -- on_use, on_secondary_use, on_place callbacks of that tool.
952 -- * reseed : If true, force map section to regenerate (if autogen is off)
953 local function seeder_use(reseed
)
954 return function(itemstack
, user
, pointed_thing
)
958 local privs
= minetest
.get_player_privs(user
:get_player_name())
959 if not privs
.server
then
960 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
963 if current_perlin
.noise
then
964 local noiseparams
= table.copy(current_perlin
.noiseparams
)
965 noiseparams
.seed
= math
.random(0, 2^
32-1)
966 set_perlin_noise(noiseparams
)
968 seeder_reseed(user
, reseed
)
970 local msg
= S("No active Perlin noise set. Set one first!")
971 minetest
.chat_send_player(user
:get_player_name(), msg
)
976 -- Converts a sidelen (=pixelize) value to a valid one
977 local fix_sidelen
= function(sidelen
)
978 return math
.max(1, math
.floor(sidelen
))
981 -- Fix some errors in the noiseparams
982 local fix_noiseparams
= function(noiseparams
)
983 noiseparams
.octaves
= math
.floor(math
.max(1, noiseparams
.octaves
))
984 noiseparams
.lacunarity
= math
.max(1.0, noiseparams
.lacunarity
)
985 noiseparams
.spread
.x
= math
.floor(math
.max(1, noiseparams
.spread
.x
))
986 noiseparams
.spread
.y
= math
.floor(math
.max(1, noiseparams
.spread
.y
))
987 noiseparams
.spread
.z
= math
.floor(math
.max(1, noiseparams
.spread
.z
))
991 -- Tool to set new random seed of the noiseparams
992 minetest
.register_tool("perlin_explorer:seeder", {
993 description
= S("Random Perlin seed setter"),
994 _tt_help
= S("Punch: Set a random seed for the active Perlin noise parameters").."\n"..
995 S("Place: Set a random seed and regenerate nodes (if applicable)"),
996 inventory_image
= "perlin_explorer_seeder.png",
997 wield_image
= "perlin_explorer_seeder.png",
998 groups
= { disable_repair
= 1 },
999 on_use
= seeder_use(false),
1000 on_secondary_use
= seeder_use(true),
1001 on_place
= seeder_use(true),
1007 minetest
.register_chatcommand("perlin_set_options", {
1008 privs
= { server
= true },
1009 description
= S("Set Perlin map generation options"),
1010 params
= S("<dimensions> <pixelize> <midpoint> <low_color> <high_color> [<node_type> <build_mode>]"),
1011 func
= function(name
, param
)
1012 local dimensions
, sidelen
, mid
, min, max, nodetype
, buildmode
= string.match(param
, "([23]) ([0-9]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9]) ([0-9])")
1013 if not dimensions
then
1014 dimensions
, sidelen
, mid
, min, max = string.match(param
, "([0-9]+) ([0-9]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+)")
1015 if not dimensions
then
1019 dimensions
= tonumber(dimensions
)
1020 sidelen
= tonumber(sidelen
)
1024 nodetype
= tonumber(nodetype
)
1025 buildmode
= tonumber(buildmode
)
1026 if not dimensions
or not sidelen
or not min or not mid
or not max then
1027 return false, S("Invalid parameter type! (only numbers are allowed)")
1029 dimensions
= math
.floor(dimensions
)
1030 if dimensions
~= 2 and dimensions
~= 3 then
1031 return false, S("Invalid dimensions! (must be 2 or 3)")
1034 -- Update current options
1035 if nodetype
and buildmode
then
1036 -- Check nodetype and buildmode
1037 nodetype
= math
.floor(nodetype
)
1038 buildmode
= math
.floor(buildmode
)
1039 if not nodetypes
[nodetype
] then
1040 return false, S("Invalid node type! (must be between 1 and @1)", #nodetypes
)
1042 if buildmode
< 1 or buildmode
> BUILDMODES_COUNT
then
1043 return false, S("Invalid build mode! (must be between 1 and @1)", BUILDMODES_COUNT
)
1045 current_options
.nodetype
= nodetype
1046 current_options
.buildmode
= buildmode
1048 current_options
.dimensions
= dimensions
1049 current_options
.sidelen
= fix_sidelen(sidelen
)
1050 current_options
.min_color
= min
1051 current_options
.mid_color
= mid
1052 current_options
.max_color
= max
1054 -- Force mapgen to update
1057 return true, S("Perlin map generation options set!")
1061 minetest
.register_chatcommand("perlin_set_noise", {
1062 privs
= { server
= true },
1063 description
= S("Set active Perlin noise parameters"),
1064 params
= S("<offset> <scale> <seed> <spread_x> <spread_y> <spread_z> <octaves> <persistence> <lacunarity> [<flags>]"),
1065 func
= function(name
, param
)
1066 local offset
, scale
, seed
, sx
, sy
, sz
, octaves
, persistence
, lacunarity
, flags
= string.match(param
, string.rep("([0-9.-]+) ", 9) .. "([a-zA-Z, ]+)")
1068 offset
, scale
, seed
, sx
, sy
, sz
, octaves
, persistence
, lacunarity
= string.match(param
, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)")
1076 octaves
= tonumber(octaves
)
1077 offset
= tonumber(offset
)
1081 persistence
= tonumber(persistence
)
1082 lacunarity
= tonumber(lacunarity
)
1083 seed
= tonumber(seed
)
1084 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
1085 return false, S("Invalid parameter type.")
1087 local noiseparams
= {
1091 spread
= { x
= sx
, y
= sy
, z
= sz
},
1092 persistence
= persistence
,
1093 lacunarity
= lacunarity
,
1097 noiseparams
= fix_noiseparams(noiseparams
)
1098 local _
, _
, _
, badwaves
= analyze_noiseparams(noiseparams
)
1100 return false, TEXT_ERROR_BAD_WAVELENGTH
1102 set_perlin_noise(noiseparams
)
1104 return true, S("Active Perlin noise parameters set!")
1108 minetest
.register_chatcommand("perlin_generate", {
1109 privs
= { server
= true },
1110 description
= S("Generate active Perlin noise"),
1111 params
= S("<pos> <size>"),
1112 func
= function(name
, param
)
1113 local x
, y
, z
, size
= string.match(param
, "([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9]+)")
1120 size
= tonumber(size
)
1121 if not x
or not y
or not z
or not size
then
1124 if not x
or not y
or not z
or not size
then
1125 return false, S("Invalid parameter type.")
1127 local pos
= vector
.new(x
, y
, z
)
1129 minetest
.chat_send_player(name
, S("Creating Perlin noise, please wait …"))
1130 local opts
= table.copy(current_options
)
1132 local msg
= create_perlin(pos
, current_perlin
.noise
, current_perlin
.noiseparams
, opts
, false)
1133 if msg
== false then
1134 return false, S("No active Perlin noise set. Set one first!")
1140 minetest
.register_chatcommand("perlin_toggle_mapgen", {
1141 privs
= { server
= true },
1142 description
= S("Toggle the automatic Perlin noise map generator"),
1144 func
= function(name
, param
)
1145 if not current_perlin
.noise
then
1146 return false, S("No active Perlin noise set. Set one first!")
1148 current_options
.autogen
= not current_options
.autogen
1149 if current_options
.autogen
then
1152 minetest
.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options
.autogen
))
1154 if current_options
.autogen
then
1155 msg
= S("Mapgen enabled!")
1157 msg
= S("Mapgen disabled!")
1163 -- Show an error window to player with the given message.
1164 -- Set analyze_button to true to add a button "Analyze now"
1165 -- to open the analyze window.
1166 local show_error_formspec
= function(player
, message
, analyze_button
)
1168 if analyze_button
then
1169 buttons
= "button[1.5,2.5;3,0.75;done;"..F(S("Return")).."]"..
1170 "button[5.5,2.5;3,0.75;analyze;"..F(S("Analyze now"))
1172 buttons
= "button[3.5,2.5;3,0.75;done;"..F(S("Return")).."]"
1176 formspec_version[4]size[10,3.5]
1177 container[0.25,0.25]
1178 box[0,0;9.5,2;]]..FORMSPEC_BOX_COLOR
..[[]
1179 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1180 label[0.25,0.2;]]..F(S("Error"))..[[]
1182 textarea[0,0;9,1.4;;]]..F(message
)..[[;]
1186 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:error", form
)
1189 -- Stats loading screen
1190 local show_histogram_loading_formspec
= function(player
)
1192 formspec_version[4]size[11,2]
1193 container[0.25,0.25]
1194 box[0,0;10.5,1.5;]]..FORMSPEC_BOX_COLOR
..[[]
1195 box[0,0;10.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1196 label[0.25,0.2;]]..F(S("Statistical analysis in progress"))..[[]
1198 label[0,0;]]..F(S("Collecting data, please wait …"))..[[]
1202 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:histogram_loading", form
)
1206 local show_histogram_formspec
= function(player
, options
, stats
)
1210 histogram
= "vertlabel[0.1,0.1;"..F(S("Relative frequency")).."]"..
1211 "label[1.1,8.15;"..F(S("Noise values")).."]"..
1212 "label[0,7.0;"..F(S("Max.")).."\n"..F(S("Min.")).."]"..
1213 "tooltip[0,6.8;0.7,1;"..F(S("Upper bounds (exclusive) and lower bounds (inclusive) of the noise value data bucketing")).."]"..
1214 "box[0.75,0;0.025,8.5;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"..
1215 "box[0,6.65;"..HISTOGRAM_BUCKETS
..",0.025;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"..
1216 "box[0,7.8;"..HISTOGRAM_BUCKETS
..",0.025;"..FORMSPEC_HISTOGRAM_LINE_COLOR
.."]"
1218 -- Special case: If bucket sizes are equal, only show the last bucket
1219 -- (can happen if scale=0)
1220 if HISTOGRAM_BUCKETS
> 1 and stats
.histogram_points
[1] == stats
.histogram_points
[2] then
1221 hstart
= HISTOGRAM_BUCKETS
1224 for h
=hstart
, HISTOGRAM_BUCKETS
do
1225 local value
= stats
.histogram
[h
]
1226 if value
> max_value
then
1230 -- Drawn histogram bars, tooltips and labels
1231 for h
=hstart
, HISTOGRAM_BUCKETS
do
1232 local count
= stats
.histogram
[h
]
1233 local ratio
= (stats
.histogram
[h
] / stats
.value_count
)
1234 local bar_ratio
= (stats
.histogram
[h
] / max_value
)
1235 local perc
= ratio
* 100
1236 local perc_f
= string.format("%.1f", perc
)
1238 local bar_height
= math
.max(maxh
* bar_ratio
, 0.001)
1239 local coords
= x
..","..maxh
-bar_height
..";0.8,"..bar_height
1242 box
= box
.. "box["..coords
..";"..FORMSPEC_HISTOGRAM_BAR_COLOR
.."]"
1243 box
= box
.. "tooltip["..coords
..";"..count
.."]"
1245 box
= box
.. "label["..x
..",6.4;"..F(S("@1%", perc_f
)).."]"
1246 box
= box
.. "tooltip["..x
..",6.2;0.9,0.3;"..count
.."]"
1248 local min, max, min_v
, max_v
1252 min = F(string.format("%.1f", stats
.histogram_points
[h
-1]))
1254 if h
>= HISTOGRAM_BUCKETS
then
1257 max = F(string.format("%.1f", stats
.histogram_points
[h
]))
1260 box
= box
.. "label["..x
..",7.0;"..max.."\n"..min.."]"
1264 tt
= F(S("value < @1", stats
.histogram_points
[h
]))
1265 elseif h
== HISTOGRAM_BUCKETS
then
1266 tt
= F(S("@1 <= value", stats
.histogram_points
[h
-1]))
1268 tt
= F(S("@1 <= value < @2", stats
.histogram_points
[h
-1], stats
.histogram_points
[h
]))
1270 box
= box
.. "tooltip["..x
..",6.8;0.9,1;"..tt
.."]"
1272 histogram
= histogram
.. box
1275 if options
.dimensions
== 2 then
1276 vmin
= S("(@1,@2)", stats
.start_pos
.x
, stats
.start_pos
.z
)
1277 vmax
= S("(@1,@2)", stats
.end_pos
.x
, stats
.end_pos
.z
)
1279 vmin
= S("(@1,@2,@3)", stats
.start_pos
.x
, stats
.start_pos
.y
, stats
.start_pos
.z
)
1280 vmax
= S("(@1,@2,@3)", stats
.end_pos
.x
, stats
.end_pos
.y
, stats
.end_pos
.z
)
1282 local labels
= "textarea[0,0;"..HISTOGRAM_BUCKETS
..",2;;;"..
1283 F(S("Values were picked randomly between noise coordinates @1 and @2.", vmin
, vmax
)).."\n"..
1284 F(S("Values calculated: @1", stats
.value_count
)).."\n"..
1285 F(S("Minimum calculated value: @1", stats
.min)).."\n"..
1286 F(S("Maximum calculated value: @1", stats
.max)).."\n"..
1287 F(S("Average calculated value: @1", stats
.avg
)).."]\n"
1290 formspec_version[4]size[]]..(HISTOGRAM_BUCKETS
+1)..[[,12.5]
1291 container[0.25,0.25]
1292 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,2.5;]]..FORMSPEC_BOX_COLOR
..[[]
1293 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1294 label[0.25,0.2;]]..F(S("Noise Value Statistics"))..[[]
1299 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,9.25;]]..FORMSPEC_BOX_COLOR
..[[]
1300 box[0,0;]]..(HISTOGRAM_BUCKETS
+0.5)..[[,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1301 label[0.25,0.2;]]..F(S("Noise Value Histogram"))..[[]
1306 button[7.25,11.35;3,0.5;done;]]..F(S("Done"))..[[]
1309 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:histogram", form
)
1312 -- Analyzes the given noise params and shows the result in a pretty-printed formspec to player
1313 local analyze_noiseparams_and_show_formspec
= function(player
, noiseparams
)
1314 local min, max, waves
= analyze_noiseparams(noiseparams
)
1315 local print_waves
= function(waves_a
)
1316 local stringified_waves
= {}
1317 for w
=1, #waves_a
do
1319 local is_bad
= false
1320 if minetest
.is_nan(waves_a
[w
]) or waves_a
[w
] == math
.huge
or waves_a
[w
] == -math
.huge
then
1321 strwave
= minetest
.colorize("#FF0000FF", waves_a
[w
])
1322 elseif waves_a
[w
] < 1 then
1323 strwave
= minetest
.colorize("#FF0000FF", "0")
1325 strwave
= string.format("%.0f", waves_a
[w
])
1327 table.insert(stringified_waves
, strwave
)
1329 return table.concat(stringified_waves
, ", ")
1332 formspec_version[4]size[10,5]
1333 container[0.25,0.25]
1334 box[0,0;9.5,3.5;]]..FORMSPEC_BOX_COLOR
..[[]
1335 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1336 label[0.25,0.2;]]..F(S("Noise Parameters Analysis"))..[[]
1338 label[0.25,0.75;]]..F(S("Minimum possible value: @1", min))..[[]
1339 label[0.25,1.25;]]..F(S("Maximum possible value: @1", max))..[[]
1341 label[0.25,1.75;]]..F(S("X wavelengths: @1", print_waves(waves
.x
)))..[[]
1342 label[0.25,2.25;]]..F(S("Y wavelengths: @1", print_waves(waves
.y
)))..[[]
1343 label[0.25,2.75;]]..F(S("Z wavelengths: @1", print_waves(waves
.z
)))..[[]
1345 button[3.5,3.75;3,0.75;done;]]..F(S("Done"))..[[]
1349 minetest
.show_formspec(player
:get_player_name(), "perlin_explorer:analyze", form
)
1352 -- Show the formspec of the Perlin Noise Creator tool to player.
1353 -- The main formspec of this mod!
1354 -- * player: Player object
1355 -- * noiseparams: Initialize formspec with these noiseparams (optional)
1356 -- * profile_id: Preselect this profile (by table index of np_profiles)
1357 -- * f_dimensions: Set to force dimensions (optional)
1358 -- * f_sidelen: Set to force sidelen (optional)
1359 local show_noise_formspec
= function(player
, noiseparams
, profile_id
, f_dimensions
, f_sidelen
)
1360 local player_name
= player
:get_player_name()
1365 np
= current_perlin
.noiseparams
1367 if not profile_id
then
1370 local offset
= tostring(np
.offset
or "")
1371 local scale
= tostring(np
.scale
or "")
1372 local seed
= tostring(np
.seed
or "")
1373 local sx
, sy
, sz
= "", "", ""
1375 sx
= tostring(np
.spread
.x
or "")
1376 sy
= tostring(np
.spread
.y
or "")
1377 sz
= tostring(np
.spread
.z
or "")
1379 local octaves
= tostring(np
.octaves
or "")
1380 local persistence
= tostring(np
.persistence
or "")
1381 local lacunarity
= tostring(np
.lacunarity
or "")
1383 local size
= tostring(current_options
.size
or "")
1384 local buildmode
= tostring(current_options
.buildmode
or 1)
1388 sidelen
= tostring(f_sidelen
)
1390 sidelen
= tostring(current_options
.sidelen
or "")
1392 local dimensions
= f_dimensions
1393 if not dimensions
then
1394 dimensions
= current_options
.dimensions
or 2
1396 local dimensions_index
= dimensions
- 1
1398 local pos_x
, pos_y
, pos_z
= "", "", ""
1399 if current_options
.pos
then
1400 pos_x
= tostring(current_options
.pos
.x
or "")
1401 pos_y
= tostring(current_options
.pos
.y
or "")
1402 pos_z
= tostring(current_options
.pos
.z
or "")
1404 local value_min
= tostring(current_options
.min_color
or "")
1405 local value_mid
= tostring(current_options
.mid_color
or "")
1406 local value_max
= tostring(current_options
.max_color
or "")
1408 local flags
= np
.flags
1409 local flags_table
= parse_flags_string(flags
)
1410 local flag_defaults
= tostring(flags_table
.defaults
)
1411 local flag_eased
= tostring(flags_table
.eased
)
1412 local flag_absvalue
= tostring(flags_table
.absvalue
)
1413 formspec_states
[player_name
].default
= flags_table
.defaults
== true
1414 formspec_states
[player_name
].absvalue
= flags_table
.absvalue
== true
1415 formspec_states
[player_name
].eased
= flags_table
.eased
== true
1417 local noiseparams_list
= {}
1419 for i
=1, #np_profiles
do
1420 local npp
= np_profiles
[i
]
1421 local name
= npp
.name
1422 -- Automatic naming for user profiles
1423 if npp
.np_type
== "user" then
1424 name
= S("Profile @1", counter
)
1425 counter
= counter
+ 1
1427 table.insert(noiseparams_list
, F(name
))
1429 local noiseparams_list_str
= table.concat(noiseparams_list
, ",")
1431 local nodetype_index
= (current_options
.nodetype
)
1433 local nodetypes_list
= {}
1434 for i
=1, #nodetypes
do
1435 table.insert(nodetypes_list
, F(nodetypes
[i
][1]))
1437 local nodetypes_list_str
= table.concat(nodetypes_list
, ",")
1439 local delete_btn
= ""
1440 if #np_profiles
> 1 then
1441 delete_btn
= "button[7.25,0;2.0,0.5;delete_np_profile;"..F(S("Delete")).."]"
1444 local create_btn
= ""
1446 if current_options
.autogen
then
1447 autogen_label
= S("Disable mapgen")
1449 autogen_label
= S("Enable mapgen")
1450 create_btn
= "button[3.50,0;3,1;create;"..F(S("Apply and create")).."]"..
1451 "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")).."]"
1453 field[0.25,1.95;1.75,0.75;pos_x;]]..F(S("X"))..[[;]]..pos_x
..[[]
1454 field[2.10,1.95;1.75,0.75;pos_y;]]..F(S("Y"))..[[;]]..pos_y
..[[]
1455 field[3.95,1.95;1.75,0.75;pos_z;]]..F(S("Z"))..[[;]]..pos_z
..[[]
1456 tooltip[pos_x;]]..F(S("Coordinate for “Apply and create”"))..[[]
1457 tooltip[pos_y;]]..F(S("Coordinate for “Apply and create”"))..[[]
1458 tooltip[pos_z;]]..F(S("Coordinate for “Apply and create”"))..[[]
1460 field[5.95,1.95;1.5,0.75;size;]]..F(S("Size"))..[[;]]..size
..[[]
1461 tooltip[size;]]..F(S("Size of area of nodes to generate (as a sidelength), used by “Apply and create”"))..[[]
1463 field_close_on_enter[pos_x;false]
1464 field_close_on_enter[pos_y;false]
1465 field_close_on_enter[pos_z;false]
1466 field_close_on_enter[size;false]
1470 formspec_version[4]size[10,12.5]
1471 container[0.25,0.25]
1472 box[0,0;9.5,5.5;]]..FORMSPEC_BOX_COLOR
..[[]
1473 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1474 label[0.15,0.2;]]..F(S("Noise parameters"))..[[]
1476 dropdown[0.25,0;3,0.5;np_profiles;]]..noiseparams_list_str
..[[;]]..profile_id
..[[;true]
1477 button[3.25,0;2.0,0.5;add_np_profile;]]..F(S("Add"))..[[]
1478 button[5.25,0;2.0,0.5;load_np_profile;]]..F(S("Load"))..[[]
1482 field[0.25,0;2,0.75;offset;]]..F(S("Offset"))..[[;]]..offset
..[[]
1483 field[3.25,0;2,0.75;scale;]]..F(S("Scale"))..[[;]]..scale
..[[]
1484 field[6.25,0;2,0.75;seed;]]..F(S("Seed"))..[[;]]..seed
..[[]
1485 image_button[8.35,0.0;0.75,0.75;perlin_explorer_seeder.png;set_random_seed;]
1486 field[0.25,1.2;2,0.75;spread_x;]]..F(S("X Spread"))..[[;]]..sx
..[[]
1487 field[3.25,1.2;2,0.75;spread_y;]]..F(S("Y Spread"))..[[;]]..sy
..[[]
1488 field[6.25,1.2;2,0.75;spread_z;]]..F(S("Z Spread"))..[[;]]..sz
..[[]
1489 field[0.25,2.4;2,0.75;octaves;]]..F(S("Octaves"))..[[;]]..octaves
..[[]
1490 field[3.25,2.4;2,0.75;persistence;]]..F(S("Persistence"))..[[;]]..persistence
..[[]
1491 field[6.25,2.4;2,0.75;lacunarity;]]..F(S("Lacunarity"))..[[;]]..lacunarity
..[[]
1492 checkbox[0.25,3.55;defaults;]]..F(S("defaults"))..[[;]]..flag_defaults
..[[]
1493 checkbox[2.25,3.55;eased;]]..F(S("eased"))..[[;]]..flag_eased
..[[]
1494 checkbox[4.25,3.55;absvalue;]]..F(S("absvalue"))..[[;]]..flag_absvalue
..[[]
1495 button[6.25,3.35;2.0,0.5;analyze;]]..F(S("Analyze"))..[[]
1498 field_close_on_enter[offset;false]
1499 field_close_on_enter[scale;false]
1500 field_close_on_enter[seed;false]
1501 field_close_on_enter[spread_x;false]
1502 field_close_on_enter[spread_y;false]
1503 field_close_on_enter[spread_z;false]
1504 field_close_on_enter[octaves;false]
1505 field_close_on_enter[persistence;false]
1506 field_close_on_enter[lacunarity;false]
1507 field_close_on_enter[sidelen;false]
1509 tooltip[set_random_seed;]]..F(S("Random seed"))..[[]
1510 tooltip[add_np_profile;]]..F(S("Add the current noise parameter as a new profile into the profile list"))..[[]
1511 tooltip[load_np_profile;]]..F(S("Load the selected profile from the profile list to replace the noise parameters"))..[[]
1512 tooltip[delete_np_profile;]]..F(S("Delete the selected profile from the profile list (only works for user profiles)"))..[[]
1513 tooltip[analyze;]]..F(S("Perform some basic mathematical analysis about these noise parameters"))..[[]
1518 box[0,0;9.5,1.6;]]..FORMSPEC_BOX_COLOR
..[[]
1519 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1520 label[0.15,0.2;]]..F(S("Noise options"))..[[]
1521 dropdown[0.25,0.7;1,0.75;dimensions;]]..F(S("2D"))..[[,]]..F(S("3D"))..[[;]]..dimensions_index
..[[;true]
1522 field[2.25,0.7;2,0.75;sidelen;]]..F(S("Pixelization"))..[[;]]..sidelen
..[[]
1523 button[6.25,0.7;2.0,0.6;statistics;]]..F(S("Statistics"))..[[]
1524 tooltip[statistics;]]..F(S("Calculate a large amount of noise values under the current settings and show a statistical analysis of these values"))..[[]
1525 tooltip[sidelen;]]..F(S("If higher than 1, Perlin values will be repeated along all axes every x nodes, for a ‘pixelized’ effect."))..[[]
1529 container[0.25,7.85]
1530 box[0,0;9.5,2.9;]]..FORMSPEC_BOX_COLOR
..[[]
1531 box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR
..[[]
1532 label[0.15,0.2;]]..F(S("Node generation"))..[[]
1533 field[0.25,0.75;1.75,0.75;value_mid;]]..F(S("Midpoint"))..[[;]]..value_mid
..[[]
1534 field[2.10,0.75;1.75,0.75;value_min;]]..F(S("Low color at"))..[[;]]..value_min
..[[]
1535 field[3.95,0.75;1.75,0.75;value_max;]]..F(S("High color at"))..[[;]]..value_max
..[[]
1536 button[5.95,0.75;1.00,0.75;autocolor;]]..F(S("Auto"))..[[]
1537 dropdown[7.55,0.75;1.9,0.75;nodetype;]]..nodetypes_list_str
..[[;]]..nodetype_index
..[[;true]
1538 tooltip[nodetype;]]..F(S("Node type: Specify the type of node that you want to generate here"))..[[]
1540 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]
1541 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"))..[[]
1542 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."))..[[]
1543 tooltip[value_min;]]..F(S("The lowest noise value to be represented by the node color gradient."))..[[]
1544 tooltip[value_max;]]..F(S("The highest noise value to be represented by the node color gradient."))..[[]
1545 tooltip[autocolor;]]..F(S("Set the midpoint and low and high color values automatically according to the theoretical noise value boundaries."))..[[]
1546 field_close_on_enter[value_mid;false]
1547 field_close_on_enter[value_min;false]
1548 field_close_on_enter[value_max;false]
1553 button[0.25,0;3.15,1;apply;]]..F(S("Apply"))..[[]
1554 tooltip[apply;]]..F(S("Set these noise parameters and noise options as the “active noise”")).."]"..
1556 button[6.60,0;3.15,1;toggle_autogen;]]..F(autogen_label
)..[[]
1557 tooltip[toggle_autogen;]]..F(S("Toggle the automatic map generator"))..[[]
1560 -- Hack to fix Minetest issue (see function comment)
1561 form
= unique_formspec_spaces(player_name
, form
)
1562 minetest
.show_formspec(player_name
, "perlin_explorer:creator", form
)
1565 -- Formspec handling
1566 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
1567 -- Require 'server' priv
1568 local privs
= minetest
.get_player_privs(player
:get_player_name())
1569 if not privs
.server
then
1574 if formname
== "perlin_explorer:analyze" or formname
== "perlin_explorer:error" then
1576 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1577 show_noise_formspec(player
, noiseparams
)
1578 elseif fields
.analyze
then
1579 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1580 analyze_noiseparams_and_show_formspec(player
, noiseparams
)
1586 if formname
== "perlin_explorer:histogram" then
1588 local noiseparams
= formspec_states
[player
:get_player_name()].noiseparams
1589 local dimensions
= formspec_states
[player
:get_player_name()].dimensions
1590 local sidelen
= formspec_states
[player
:get_player_name()].sidelen
1591 show_noise_formspec(player
, noiseparams
, nil, dimensions
, sidelen
)
1597 if formname
~= "perlin_explorer:creator" then
1601 -- Handle checkboxes
1602 local player_name
= player
:get_player_name()
1603 local flags_touched
= false
1604 if fields
.defaults
== "true" then
1605 formspec_states
[player_name
].defaults
= true
1607 elseif fields
.defaults
== "false" then
1608 formspec_states
[player_name
].defaults
= false
1611 if fields
.eased
== "true" then
1612 formspec_states
[player_name
].eased
= true
1614 elseif fields
.eased
== "false" then
1615 formspec_states
[player_name
].eased
= false
1618 if fields
.absvalue
== "true" then
1619 formspec_states
[player_name
].absvalue
= true
1621 elseif fields
.absvalue
== "false" then
1622 formspec_states
[player_name
].absvalue
= false
1626 -- Deleting a profile does not require any other field
1627 if fields
.delete_np_profile
then
1628 if #np_profiles
<= 1 then
1631 local profile_to_delete
= tonumber(fields
.np_profiles
)
1632 -- Can only delete user profiles
1633 if np_profiles
[profile_to_delete
].np_type
~= "user" then
1636 table.remove(np_profiles
, profile_to_delete
)
1637 update_mod_stored_profiles()
1638 local new_id
= math
.max(1, profile_to_delete
- 1)
1639 show_noise_formspec(player
, default_noiseparams
, new_id
)
1642 -- Handle other fields
1643 local enter_pressed
= fields
.key_enter_field
~= nil
1644 local do_apply
= fields
.apply
~= nil or fields
.toggle_autogen
~= nil
1645 local do_create
= fields
.create
~= nil
1646 if current_options
.autogen
then
1647 do_apply
= do_apply
or enter_pressed
1649 do_create
= do_create
or enter_pressed
1651 local do_analyze
= fields
.analyze
~= nil
1652 if (do_create
or do_apply
or do_analyze
or fields
.add_np_profile
or fields
.np_profiles
) then
1653 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
1655 local offset
= tonumber(fields
.offset
)
1656 local scale
= tonumber(fields
.scale
)
1657 local seed
= tonumber(fields
.seed
)
1658 local sx
= tonumber(fields
.spread_x
)
1659 local sy
= tonumber(fields
.spread_y
)
1660 local sz
= tonumber(fields
.spread_z
)
1661 if not sx
or not sy
or not sz
then
1664 local spread
= vector
.new(sx
, sy
, sz
)
1665 local octaves
= tonumber(fields
.octaves
)
1666 local persistence
= tonumber(fields
.persistence
)
1667 local lacunarity
= tonumber(fields
.lacunarity
)
1668 local dimensions
= tonumber(fields
.dimensions
)
1669 local sidelen
= tonumber(fields
.sidelen
)
1670 local px
= tonumber(fields
.pos_x
)
1671 local py
= tonumber(fields
.pos_y
)
1672 local pz
= tonumber(fields
.pos_z
)
1673 local size
= tonumber(fields
.size
)
1674 local value_min
= tonumber(fields
.value_min
)
1675 local value_mid
= tonumber(fields
.value_mid
)
1676 local value_max
= tonumber(fields
.value_max
)
1677 local nodetype
= tonumber(fields
.nodetype
)
1678 local buildmode
= tonumber(fields
.buildmode
)
1679 if (offset
and scale
and spread
and octaves
and persistence
) then
1680 local defaults
= formspec_states
[player_name
].defaults
1681 local eased
= formspec_states
[player_name
].eased
1682 local absvalue
= formspec_states
[player_name
].absvalue
1683 local noiseparams
= {
1689 persistence
= persistence
,
1690 lacunarity
= lacunarity
,
1691 flags
= build_flags_string(defaults
, eased
, absvalue
),
1693 noiseparams
= fix_noiseparams(noiseparams
)
1694 -- Open analyze window
1696 formspec_states
[player_name
].noiseparams
= noiseparams
1697 analyze_noiseparams_and_show_formspec(player
, noiseparams
)
1699 -- Change NP profile selection
1700 elseif fields
.load_np_profile
and fields
.np_profiles
then
1701 local profile
= tonumber(fields
.np_profiles
)
1703 if np_profiles
[profile
].np_type
== "active" then
1704 -- The "active" profile is a special profile
1705 -- that reads from the active noiseparams.
1706 loaded_np
= current_perlin
.noiseparams
1708 -- Normal case: Read noiseparams from profile itself.
1709 loaded_np
= np_profiles
[profile
].noiseparams
1712 show_noise_formspec(player
, loaded_np
, profile
)
1713 minetest
.log("action", "[perlin_explorer] Loaded perlin noise profile "..profile
)
1715 -- Add new profile and save current noiseparams to it
1716 elseif fields
.add_np_profile
then
1717 table.insert(np_profiles
, {
1719 noiseparams
= noiseparams
,
1721 update_mod_stored_profiles()
1722 local new_profile
= #np_profiles
1723 minetest
.log("action", "[perlin_explorer] Perlin noise profile "..new_profile
.." added!")
1724 show_noise_formspec(player
, noiseparams
, new_profile
)
1726 elseif fields
.set_random_seed
then
1728 local profile
= tonumber(fields
.np_profiles
)
1729 noiseparams
.seed
= math
.random(0, 2^
32-1)
1730 show_noise_formspec(player
, noiseparams
, profile
)
1732 elseif fields
.autocolor
then
1733 -- Set color fields automatically
1734 local min, max = analyze_noiseparams(noiseparams
)
1735 current_options
.min_color
= min
1736 current_options
.max_color
= max
1737 current_options
.mid_color
= min + ((max - min) / 2)
1738 local profile
= tonumber(fields
.np_profiles
)
1739 show_noise_formspec(player
, noiseparams
, profile
)
1743 if not (dimensions
and sidelen
and value_min
and value_mid
and value_max
and nodetype
and buildmode
) then
1746 -- Convert dropdown index to actual dimensions number
1747 dimensions
= dimensions
+ 1
1748 -- Spread is used differently in 2D
1749 if dimensions
== 2 then
1752 local _
, _
, _
, badwaves
= analyze_noiseparams(noiseparams
)
1754 formspec_states
[player_name
].noiseparams
= noiseparams
1755 show_error_formspec(player
, TEXT_ERROR_BAD_WAVELENGTH
, true)
1759 -- Start statistics calculation
1760 if fields
.statistics
then
1761 formspec_states
[player_name
].noiseparams
= noiseparams
1762 formspec_states
[player_name
].dimensions
= dimensions
1763 formspec_states
[player_name
].sidelen
= sidelen
1764 local max_spread
= 0
1765 max_spread
= math
.max(max_spread
, noiseparams
.spread
.x
)
1766 max_spread
= math
.max(max_spread
, noiseparams
.spread
.y
)
1767 max_spread
= math
.max(max_spread
, noiseparams
.spread
.z
)
1768 local ssize
= max_spread
* STATISTICS_SPREAD_FACTOR
1769 -- A very large size is not allowed because the Minetest functions start to fail and would start to distort the statistics.
1770 if ssize
> STATISTICS_MAX_SIZE
or max_spread
> STATISTICS_MAX_SPREAD
then
1771 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)
1774 -- Enforce a minimum size as well to at least 1 million values are in the area
1775 if dimensions
== 2 and ssize
< 1000 then
1777 elseif dimensions
== 3 and ssize
< 100 then
1780 local start
= -math
.floor(ssize
/2)
1782 local pos
= vector
.new(start
, start
, start
)
1783 local noise
= PerlinNoise(noiseparams
)
1785 dimensions
= dimensions
,
1789 -- Show a loading formspec
1790 show_histogram_loading_formspec(player
)
1792 local _
, stats
= create_perlin(pos
, noise
, noiseparams
, options
, true)
1794 -- Update the formspec to show the result
1795 show_histogram_formspec(player
, options
, stats
)
1797 minetest
.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
1803 set_perlin_noise(noiseparams
)
1804 minetest
.log("action", "[perlin_explorer] Perlin noise set!")
1805 current_options
.dimensions
= dimensions
1806 current_options
.sidelen
= fix_sidelen(sidelen
)
1807 current_options
.min_color
= value_min
1808 current_options
.mid_color
= value_mid
1809 current_options
.max_color
= value_max
1810 current_options
.nodetype
= nodetype
1811 current_options
.buildmode
= buildmode
1812 if fields
.toggle_autogen
then
1813 current_options
.autogen
= not current_options
.autogen
1814 if current_options
.autogen
then
1817 local profile
= tonumber(fields
.np_profiles
)
1818 show_noise_formspec(player
, noiseparams
, profile
)
1819 minetest
.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options
.autogen
))
1820 elseif do_create
then
1821 if not px
or not py
or not pz
or not size
then
1824 if current_options
.autogen
then
1827 current_options
.size
= size
1828 local place_pos
= vector
.new(px
, py
, pz
)
1829 current_options
.pos
= place_pos
1830 local msg
= S("Creating Perlin noise, please wait …")
1831 minetest
.chat_send_player(player_name
, msg
)
1832 msg
= create_perlin(place_pos
, current_perlin
.noise
, current_perlin
.noiseparams
, current_options
, false)
1834 minetest
.chat_send_player(player_name
, msg
)
1835 elseif msg
== false then
1836 minetest
.log("error", "[perlin_explorer] Error generating Perlin noise nodes!")
1838 elseif do_apply
and current_options
.autogen
then
1840 elseif do_apply
and not current_options
.autogen
then
1841 if not px
or not py
or not pz
then
1844 local place_pos
= vector
.new(px
, py
, pz
)
1845 current_options
.pos
= place_pos
1852 -- The main tool of this mod. Opens the Perlin noise creation window.
1853 minetest
.register_tool("perlin_explorer:creator", {
1854 description
= S("Perlin Noise Creator"),
1855 _tt_help
= S("Punch to open the Perlin noise creation menu"),
1856 inventory_image
= "perlin_explorer_creator.png",
1857 wield_image
= "perlin_explorer_creator.png",
1858 groups
= { disable_repair
= 1 },
1859 on_use
= function(itemstack
, user
, pointed_thing
)
1863 local privs
= minetest
.get_player_privs(user
:get_player_name())
1864 if not privs
.server
then
1865 minetest
.chat_send_player(user
:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server"))
1868 show_noise_formspec(user
)
1872 -- Automatic map generation (autogen).
1873 -- The autogen is kind of a mapgen, but it doesn't use Minetest's
1874 -- mapgen, instead it writes directly to map. The benefit is
1875 -- that this will instantly update when the active noiseparams
1877 -- This will generate so-called noisechunks, which are like
1878 -- Minetest mapchunks, except with respect to this mod only.
1880 minetest
.register_globalstep(function(dtime
)
1881 timer
= timer
+ dtime
1882 if timer
< AUTOBUILD_UPDATE_TIME
then
1886 if current_perlin
.noise
and current_options
.autogen
then
1887 local players
= minetest
.get_connected_players()
1888 -- Generate close to all connected players
1889 for p
=1, #players
do
1890 local player
= players
[p
]
1891 local player_name
= player
:get_player_name()
1892 -- Helper function for building
1893 local build
= function(pos
, pos_hash
, player_name
)
1894 if not pos
or not pos
.x
or not pos
.y
or not pos
.z
then
1895 minetest
.log("error", "[perlin_explorer] build(): Invalid pos!")
1898 if not pos_hash
then
1899 minetest
.log("error", "[perlin_explorer] build(): Invalid pos_hash!")
1902 if not loaded_areas
[pos_hash
] then
1903 local opts
= table.copy(current_options
)
1904 opts
.size
= AUTOBUILD_SIZE
1905 -- This actually builds the nodes
1906 create_perlin(pos
, current_perlin
.noise
, current_perlin
.noiseparams
, opts
, false)
1907 -- Built chunks are marked so they don't get generated over and over
1909 loaded_areas
[pos_hash
] = true
1913 -- Build list of coordinates to generate nodes in
1914 local pos
= vector
.round(player
:get_pos())
1915 pos
= sidelen_pos(pos
, AUTOBUILD_SIZE
)
1916 local neighbors
= { vector
.new(0, 0, 0) }
1917 local c
= AUTOBUILD_CHUNKDIST
1919 if current_options
.dimensions
== 2 then
1925 table.insert(neighbors
, vector
.new(cx
, cy
, cz
))
1929 local noisechunks
= {}
1930 for n
=1, #neighbors
do
1931 local offset
= vector
.multiply(neighbors
[n
], AUTOBUILD_SIZE
)
1932 local npos
= vector
.add(pos
, offset
)
1933 -- Check of position is still within the valid map
1934 local node
= minetest
.get_node_or_nil(npos
)
1936 local hash
= minetest
.hash_node_position(npos
)
1937 if not loaded_areas
[hash
] then
1938 table.insert(noisechunks
, {npos
, hash
})
1942 if #noisechunks
> 0 then
1943 minetest
.log("verbose", "[perlin_explorer] Started building "..#noisechunks
.." noisechunk(s)")
1946 for c
=1, #noisechunks
do
1947 local npos
= noisechunks
[c
][1]
1948 local nhash
= noisechunks
[c
][2]
1949 build(npos
, nhash
, player_name
)
1951 if #noisechunks
> 0 then
1952 minetest
.log("verbose", "[perlin_explorer] Done building "..#noisechunks
.." noisechunk(s)")
1958 -- Player variable init and cleanup
1959 minetest
.register_on_joinplayer(function(player
)
1960 local name
= player
:get_player_name()
1961 local flags
= default_noiseparams
.flags
1962 local flags_table
= parse_flags_string(flags
)
1963 formspec_states
[name
] = {
1964 defaults
= flags_table
.defaults
,
1965 eased
= flags_table
.eased
,
1966 absvalue
= flags_table
.absvalue
,
1969 noiseparams
= table.copy(default_noiseparams
),
1971 -- This is used by the `unique_formspec_spaces` hack
1972 sequence_number
= 0,
1975 minetest
.register_on_leaveplayer(function(player
)
1976 local name
= player
:get_player_name()
1977 formspec_states
[name
] = nil