Add missing mapgen noise parameters
[minetest_perlin_explorer.git] / init.lua
blobe6cd6bafc1105d945b44dc4ca4ae8c675fe9660b
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
29 [128] = 2,
30 [64] = 4,
31 [32] = 8,
32 [16] = 16,
33 [8] = 32,
34 [4] = 64,
35 [2] = 128,
36 [1] = 256,
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 ...")
43 end
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
79 -- noise spread.
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
83 -- in this range.
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)
99 -- * np_type: Type
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 = {
108 offset = 0.0,
109 scale = 1.0,
110 spread = vector.new(10, 10, 10),
111 seed = 0,
112 octaves = 2,
113 persistence = 0.5,
114 lacunarity = 2.0,
115 flags = "defaults",
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)
140 if np then
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)
150 local loaded = 0
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, {
156 np_type = "user",
157 noiseparams = user_profiles[p].noiseparams,
159 loaded = loaded + 1
160 else
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
186 -- to be generated.
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}
239 if sidelen <= 1 then
240 return newpos
242 newpos.x = newpos.x - newpos.x % sidelen
243 newpos.y = newpos.y - newpos.y % sidelen
244 newpos.z = newpos.z - newpos.z % sidelen
245 return newpos
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
261 -- this.
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
267 seq = (seq + 1) % 4
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)
276 local flagst = {}
277 if defaults then
278 table.insert(flagst, "defaults")
280 if eased then
281 table.insert(flagst, "eased")
282 else
283 table.insert(flagst, "noeased")
285 if absvalue then
286 table.insert(flagst, "absvalue")
287 else
288 table.insert(flagst, "noabsvalue")
290 local flags = table.concat(flagst, ",")
291 return flags
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
298 -- }.
299 local parse_flags_string = function(flags)
300 local ftable = string.split(flags, ",")
301 local defaults, eased, absvalue = false, false, false
302 for f=1, #ftable do
303 local s = string.trim(ftable[f])
304 if s == "defaults" then
305 defaults = true
306 elseif s == "eased" then
307 eased = true
308 elseif s == "absvalue" then
309 absvalue = true
312 if not defaults and not eased and not absvalue then
313 defaults = true
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
326 local nodetypes = {
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
345 -- Octaves
346 local o_min, o_max = 0, 0
347 for o=1, np.octaves do
348 local exp = o-1
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" }
369 local waves = {}
370 for a=1, #axes do
371 local w = axes[a]
372 waves[w] = {}
373 local wave = np.spread[w]
374 for o=1, np.octaves do
375 if wave < 1 then
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"]
388 if stone then
389 local desc = minetest.registered_nodes[stone].description
390 if not desc then
391 desc = stone
393 table.insert(nodetypes, { desc, stone, "air", false})
395 end)
397 -----------
398 -- Nodes --
399 -----------
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
407 paramtype2 = "color"
409 if grayscale_colors then
410 palette = "perlin_explorer_node_palette_gray.png"
411 palette_low = "perlin_explorer_node_palette_gray_low.png"
412 else
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)"),
420 paramtype = "light",
421 -- Intentionally does not cast shadow so that
422 -- cave structures are always fullbright when
423 -- the sun shines.
424 sunlight_propagates = true,
425 paramtype2 = paramtype2,
426 tiles = {"perlin_explorer_node.png"},
427 palette = palette,
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)"),
434 paramtype = "light",
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)"),
448 paramtype = "light",
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",
455 palette = palette,
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)"),
461 paramtype = "light",
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)"),
477 paramtype = "light",
478 drawtype = "nodebox",
479 climbable = true,
480 walkable = false,
481 node_box = {
482 type = "fixed",
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"},
489 palette = palette,
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)"),
495 paramtype = "light",
496 drawtype = "nodebox",
497 climbable = true,
498 walkable = false,
499 node_box = {
500 type = "fixed",
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)
521 local val
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)
527 else
528 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
529 return
531 local msg
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)
539 else
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)
547 if not user then
548 return
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"))
553 return
555 if current_perlin.noise then
556 local pos = user:get_pos()
557 local ctrl = user:get_player_control()
558 local precision = 1
559 if not ctrl.sneak then
560 pos = vector.round(pos)
561 precision = 0
563 print_value(pos, user, precision, "player")
564 else
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)
572 if not user then
573 return
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"))
578 return
580 if current_perlin.noise then
581 if pointed_thing.type ~= "node" then
582 -- No-op for non-nodes
583 return
585 local pos = pointed_thing.under
586 print_value(pos, user, 0, "node")
587 else
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 },
601 on_use = use_getter,
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>,
622 -- ... etc. ...
623 [HISTOGRAM_BUCKETS] = <number of values in last bucket>,
625 histogram_points, -- List of cutoff points for each of the
626 -- data buckets
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)
633 -- Init
634 local stats
635 if not noise then
636 return
638 local time1 = minetest.get_us_time()
640 local size_v = {
641 x = options.size,
642 y = options.size,
643 z = options.size,
646 local startpos = pos
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
652 y_max = 0
653 startpos.y = pos.y
654 endpos.y = pos.y
655 size_v.z = nil
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]
673 -- Init stats
674 stats = {}
675 stats.avg = 0
676 local sum_of_values = 0
677 stats.value_count = 0
679 stats.histogram = {}
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
689 local perlin_map
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})
696 else
697 perlin_map = perlin_map_object:get_3d_map(startpos)
701 local x_max, z_max
702 if stats_mode then
703 x_max = STATISTICS_ITERATIONS - 1
704 y_max = 0
705 z_max = 0
706 else
707 x_max = endpos.x - startpos.x
708 z_max = endpos.z - startpos.z
711 -- Main loop (time-critical!)
712 for x=0, x_max do
713 for y=0, y_max do
714 for z=0, z_max do
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
718 -- loop
720 -- Get Perlin value at current pos
721 local abspos
722 if not stats_mode then
723 abspos = {
724 x = startpos.x + x,
725 y = startpos.y + y,
726 z = startpos.z + z,
728 elseif stats_mode then
729 abspos = {
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)
737 local indexpos = {
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
744 local perlin_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
751 -- edges.
752 -- Ideally, for cleaner code, the precalculated map would
753 -- take this into account as well but this has not
754 -- been done yet.
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})
758 else
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
764 -- See above
765 perlin_value = noise:get_3d(abspos_get)
766 else
767 -- See above
768 perlin_value = perlin_map[indexpos.z][indexpos.y][indexpos.x]
770 else
771 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
772 return
775 -- Statistics
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
786 -- Histogram
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
790 break
793 -- More stats
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
807 else
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)
817 -- Get vmanip index
818 local index = varea:indexp(abspos)
819 if not index then
820 return
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
828 else
829 vdata[index] = minetest.CONTENT_AIR
830 vdata2[index] = 0
832 else
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
836 else
837 vdata[index] = minetest.CONTENT_AIR
838 vdata2[index] = 0
845 -- Final stats
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")
862 return stats
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)
875 if not noise then
876 return false
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
890 else
891 center.y = mpos.y + options.size/2
893 center.z = mpos.z + options.size/2
894 minetest.add_particle({
895 pos = center,
896 expirationtime = 4,
897 size = 16,
898 texture = "perlin_explorer_new_noisechunk.png",
899 glow = minetest.LIGHT_MAX,
903 if stats then
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
906 else
907 minetest.log("error", "[perlin_explorer] Could not get stats!")
908 return false
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)
916 local msg
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)
921 if msg ~= false then
922 minetest.chat_send_player(player:get_player_name(), msg)
924 else
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)
935 if not user then
936 return
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"))
941 return
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)
947 loaded_areas = {}
948 seeder_reseed(user, reseed)
949 else
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))
968 return noiseparams
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),
984 -------------------
985 -- Chat commands --
986 -------------------
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
996 return false
999 dimensions = tonumber(dimensions)
1000 sidelen = tonumber(sidelen)
1001 min = tonumber(min)
1002 mid = tonumber(mid)
1003 max = tonumber(max)
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
1035 loaded_areas = {}
1037 return true, S("Perlin map generation options set!")
1038 end,
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, ]+)")
1047 if not offset then
1048 offset, scale, seed, sx, sy, sz, octaves, persistence, lacunarity = string.match(param, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)")
1049 if not offset then
1050 return false
1053 if not flags then
1054 flags = ""
1056 octaves = tonumber(octaves)
1057 offset = tonumber(offset)
1058 sx = tonumber(sx)
1059 sy = tonumber(sy)
1060 sz = tonumber(sz)
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 = {
1068 octaves = octaves,
1069 offset = offset,
1070 scale = scale,
1071 spread = { x = sx, y = sy, z = sz },
1072 persistence = persistence,
1073 lacunarity = lacunarity,
1074 seed = seed,
1075 flags = flags,
1077 noiseparams = fix_noiseparams(noiseparams)
1078 local _, _, _, badwaves = analyze_noiseparams(noiseparams)
1079 if badwaves then
1080 return false, TEXT_ERROR_BAD_WAVELENGTH
1082 set_perlin_noise(noiseparams)
1083 loaded_areas = {}
1084 return true, S("Active Perlin noise parameters set!")
1085 end,
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]+)")
1094 if not x then
1095 return false
1097 x = tonumber(x)
1098 y = tonumber(y)
1099 z = tonumber(z)
1100 size = tonumber(size)
1101 if not x or not y or not z or not size then
1102 return false
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)
1111 opts.size = size
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!")
1116 return true, msg
1117 end,
1120 minetest.register_chatcommand("perlin_toggle_mapgen", {
1121 privs = { server = true },
1122 description = S("Toggle the automatic Perlin noise map generator"),
1123 params = "",
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
1130 loaded_areas = {}
1132 minetest.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options.autogen))
1133 local msg
1134 if current_options.autogen then
1135 msg = S("Mapgen enabled!")
1136 else
1137 msg = S("Mapgen disabled!")
1139 return true, msg
1140 end,
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)
1147 local buttons
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"))
1151 else
1152 buttons = "button[3.5,2.5;3,0.75;done;"..F(S("Return")).."]"
1155 local form = [[
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"))..[[]
1161 container[0.25,0.5]
1162 textarea[0,0;9,1.4;;]]..F(message)..[[;]
1163 container_end[]
1164 container_end[]
1165 ]]..buttons
1166 minetest.show_formspec(player:get_player_name(), "perlin_explorer:error", form)
1169 -- Stats loading screen
1170 local show_histogram_loading_formspec = function(player)
1171 local form = [[
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"))..[[]
1177 container[0.25,0.8]
1178 label[0,0;]]..F(S("Collecting data, please wait …"))..[[]
1179 container_end[]
1180 container_end[]
1182 minetest.show_formspec(player:get_player_name(), "perlin_explorer:histogram_loading", form)
1185 -- Stats screen
1186 local show_histogram_formspec = function(player, options, stats)
1187 local txt = ""
1188 local maxh = 6.0
1189 local histogram
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.."]"
1197 local hstart = 1
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
1203 local max_value = 0
1204 for h=hstart, HISTOGRAM_BUCKETS do
1205 local value = stats.histogram[h]
1206 if value > max_value then
1207 max_value = value
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)
1217 local x = h * 0.9
1218 local bar_height = math.max(maxh * bar_ratio, 0.001)
1219 local coords = x..","..maxh-bar_height..";0.8,"..bar_height
1220 local box = ""
1221 if count > 0 then
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
1229 if h <= 1 then
1230 min = ""
1231 else
1232 min = F(string.format("%.1f", stats.histogram_points[h-1]))
1234 if h >= HISTOGRAM_BUCKETS then
1235 max = ""
1236 else
1237 max = F(string.format("%.1f", stats.histogram_points[h]))
1240 box = box .. "label["..x..",7.0;"..max.."\n"..min.."]"
1242 local tt
1243 if h == 1 then
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]))
1247 else
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
1254 local vmin, vmax
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)
1258 else
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"
1269 local form = [[
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"))..[[]
1275 container[0.25,0.5]
1276 ]]..labels..[[
1277 container_end[]
1278 container[0,2.75]
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"))..[[]
1282 container[0.25,0.6]
1283 ]]..histogram..[[
1284 container_end[]
1285 container_end[]
1286 button[7.25,11.35;3,0.5;done;]]..F(S("Done"))..[[]
1287 container_end[]
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
1298 local strwave
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")
1304 else
1305 strwave = string.format("%.0f", waves_a[w])
1307 table.insert(stringified_waves, strwave)
1309 return table.concat(stringified_waves, ", ")
1311 local form = [[
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"))..[[]
1327 container_end[]
1328 --]]
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()
1341 local np
1342 if noiseparams then
1343 np = noiseparams
1344 else
1345 np = current_perlin.noiseparams
1347 if not profile_id then
1348 profile_id = 1
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 = "", "", ""
1354 if np.spread then
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)
1366 local sidelen
1367 if f_sidelen then
1368 sidelen = tostring(f_sidelen)
1369 else
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 = {}
1398 local counter = 1
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")).."]"
1423 local autogen_label
1424 local create_btn = ""
1425 local xyzsize = ""
1426 if current_options.autogen then
1427 autogen_label = S("Disable mapgen")
1428 else
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")).."]"
1432 xyzsize = [[
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]
1449 local form = [[
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"))..[[]
1455 container[0.0,0.5]
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"))..[[]
1459 ]]..delete_btn..[[
1460 container_end[]
1461 container[0.0,1.5]
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"))..[[]
1476 container_end[]
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"))..[[]
1494 container_end[]
1497 container[0.25,6.0]
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."))..[[]
1506 container_end[]
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"))..[[]
1519 ]]..xyzsize..[[
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]
1529 container_end[]
1532 container[0,10.95]
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”")).."]"..
1535 create_btn..[[
1536 button[6.60,0;3.15,1;toggle_autogen;]]..F(autogen_label)..[[]
1537 tooltip[toggle_autogen;]]..F(S("Toggle the automatic map generator"))..[[]
1538 container_end[]
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
1550 return
1553 -- Analysis window
1554 if formname == "perlin_explorer:analyze" or formname == "perlin_explorer:error" then
1555 if fields.done 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)
1562 return
1565 -- Histogram window
1566 if formname == "perlin_explorer:histogram" then
1567 if fields.done 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)
1573 return
1576 -- Creator window
1577 if formname ~= "perlin_explorer:creator" then
1578 return
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
1586 return
1587 elseif fields.defaults == "false" then
1588 formspec_states[player_name].defaults = false
1589 return
1591 if fields.eased == "true" then
1592 formspec_states[player_name].eased = true
1593 return
1594 elseif fields.eased == "false" then
1595 formspec_states[player_name].eased = false
1596 return
1598 if fields.absvalue == "true" then
1599 formspec_states[player_name].absvalue = true
1600 return
1601 elseif fields.absvalue == "false" then
1602 formspec_states[player_name].absvalue = false
1603 return
1606 -- Deleting a profile does not require any other field
1607 if fields.delete_np_profile then
1608 if #np_profiles <= 1 then
1609 return
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
1614 return
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)
1620 return
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
1628 else
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
1642 return
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 = {
1664 offset = offset,
1665 scale = scale,
1666 seed = seed,
1667 spread = spread,
1668 octaves = octaves,
1669 persistence = persistence,
1670 lacunarity = lacunarity,
1671 flags = build_flags_string(defaults, eased, absvalue),
1673 noiseparams = fix_noiseparams(noiseparams)
1674 -- Open analyze window
1675 if do_analyze then
1676 formspec_states[player_name].noiseparams = noiseparams
1677 analyze_noiseparams_and_show_formspec(player, noiseparams)
1678 return
1679 -- Change NP profile selection
1680 elseif fields.load_np_profile and fields.np_profiles then
1681 local profile = tonumber(fields.np_profiles)
1682 local loaded_np
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
1687 else
1688 -- Normal case: Read noiseparams from profile itself.
1689 loaded_np = np_profiles[profile].noiseparams
1691 -- Load new profile
1692 show_noise_formspec(player, loaded_np, profile)
1693 minetest.log("action", "[perlin_explorer] Loaded perlin noise profile "..profile)
1694 return
1695 -- Add new profile and save current noiseparams to it
1696 elseif fields.add_np_profile then
1697 table.insert(np_profiles, {
1698 np_type = "user",
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)
1705 return
1706 elseif fields.set_random_seed then
1707 -- Randomize seed
1708 local profile = tonumber(fields.np_profiles)
1709 noiseparams.seed = math.random(0, 2^32-1)
1710 show_noise_formspec(player, noiseparams, profile)
1711 return
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)
1720 return
1723 if not (dimensions and sidelen and value_min and value_mid and value_max and nodetype and buildmode) then
1724 return
1726 -- Convert dropdown index to actual dimensions number
1727 dimensions = dimensions + 1
1728 -- Spread is used differently in 2D
1729 if dimensions == 2 then
1730 spread.y = spread.z
1732 local _, _, _, badwaves = analyze_noiseparams(noiseparams)
1733 if badwaves then
1734 formspec_states[player_name].noiseparams = noiseparams
1735 show_error_formspec(player, TEXT_ERROR_BAD_WAVELENGTH, true)
1736 return
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)
1752 return
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
1756 ssize = 1000
1757 elseif dimensions == 3 and ssize < 100 then
1758 ssize = 100
1760 local start = -math.floor(ssize/2)
1762 local pos = vector.new(start, start, start)
1763 local noise = PerlinNoise(noiseparams)
1764 local options = {
1765 dimensions = dimensions,
1766 size = ssize,
1767 sidelen = sidelen,
1769 -- Show a loading formspec
1770 show_histogram_loading_formspec(player)
1771 -- This takes long
1772 local _, stats = create_perlin(pos, noise, noiseparams, options, true)
1773 if stats then
1774 -- Update the formspec to show the result
1775 show_histogram_formspec(player, options, stats)
1776 else
1777 minetest.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
1779 return
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
1795 loaded_areas = {}
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
1802 return
1804 if current_options.autogen then
1805 loaded_areas = {}
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)
1813 if msg then
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
1819 loaded_areas = {}
1820 elseif do_apply and not current_options.autogen then
1821 if not px or not py or not pz then
1822 return
1824 local place_pos = vector.new(px, py, pz)
1825 current_options.pos = place_pos
1830 end)
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)
1840 if not user then
1841 return
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"))
1846 return
1848 show_noise_formspec(user)
1849 end,
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
1856 -- are changed.
1857 -- This will generate so-called noisechunks, which are like
1858 -- Minetest mapchunks, except with respect to this mod only.
1859 local timer = 0
1860 minetest.register_globalstep(function(dtime)
1861 timer = timer + dtime
1862 if timer < AUTOBUILD_UPDATE_TIME then
1863 return
1865 timer = 0
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!")
1876 return
1878 if not pos_hash then
1879 minetest.log("error", "[perlin_explorer] build(): Invalid pos_hash!")
1880 return
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
1888 -- again
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
1898 local cc = c
1899 if current_options.dimensions == 2 then
1900 cc = 0
1902 for cx=-c, c do
1903 for cy=-cc, cc do
1904 for cz=-c, c do
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)
1915 if node ~= nil then
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)")
1925 -- Build the nodes!
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)")
1936 end)
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,
1947 sidelen = 1,
1948 dimensions = 2,
1949 noiseparams = table.copy(default_noiseparams),
1951 -- This is used by the `unique_formspec_spaces` hack
1952 sequence_number = 0,
1954 end)
1955 minetest.register_on_leaveplayer(function(player)
1956 local name = player:get_player_name()
1957 formspec_states[name] = nil
1958 end)