Version 1.0.3
[minetest_perlin_explorer.git] / init.lua
blobf23159ca162c1d45b3cc6d4b5bb8ee87c3b1af23
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 -- Calculate the two possible extreme values
350 -- with the octave value being either at 1 or -1.
351 local limit1 = (1 * np.persistence ^ exp)
352 local limit2
353 if not is_absolute then
354 limit2 = (-1 * np.persistence ^ exp)
355 else
356 -- If absvalue is set, one of the
357 -- limits is always 0 because we
358 -- can't get lower.
359 limit2 = 0
362 -- To add to the maximum, pick the higher value
363 if limit1 > limit2 then
364 o_max = o_max + limit1
365 else
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
372 else
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" }
389 local waves = {}
390 for a=1, #axes do
391 local w = axes[a]
392 waves[w] = {}
393 local wave = np.spread[w]
394 for o=1, np.octaves do
395 if wave < 1 then
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"]
408 if stone then
409 local desc = minetest.registered_nodes[stone].description
410 if not desc then
411 desc = stone
413 table.insert(nodetypes, { desc, stone, "air", false})
415 end)
417 -----------
418 -- Nodes --
419 -----------
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
427 paramtype2 = "color"
429 if grayscale_colors then
430 palette = "perlin_explorer_node_palette_gray.png"
431 palette_low = "perlin_explorer_node_palette_gray_low.png"
432 else
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)"),
440 paramtype = "light",
441 -- Intentionally does not cast shadow so that
442 -- cave structures are always fullbright when
443 -- the sun shines.
444 sunlight_propagates = true,
445 paramtype2 = paramtype2,
446 tiles = {"perlin_explorer_node.png"},
447 palette = palette,
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)"),
454 paramtype = "light",
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)"),
468 paramtype = "light",
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",
475 palette = palette,
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)"),
481 paramtype = "light",
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)"),
497 paramtype = "light",
498 drawtype = "nodebox",
499 climbable = true,
500 walkable = false,
501 node_box = {
502 type = "fixed",
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"},
509 palette = palette,
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)"),
515 paramtype = "light",
516 drawtype = "nodebox",
517 climbable = true,
518 walkable = false,
519 node_box = {
520 type = "fixed",
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)
541 local val
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)
547 else
548 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
549 return
551 local msg
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)
559 else
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)
567 if not user then
568 return
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"))
573 return
575 if current_perlin.noise then
576 local pos = user:get_pos()
577 local ctrl = user:get_player_control()
578 local precision = 1
579 if not ctrl.sneak then
580 pos = vector.round(pos)
581 precision = 0
583 print_value(pos, user, precision, "player")
584 else
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)
592 if not user then
593 return
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"))
598 return
600 if current_perlin.noise then
601 if pointed_thing.type ~= "node" then
602 -- No-op for non-nodes
603 return
605 local pos = pointed_thing.under
606 print_value(pos, user, 0, "node")
607 else
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 },
621 on_use = use_getter,
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>,
642 -- ... etc. ...
643 [HISTOGRAM_BUCKETS] = <number of values in last bucket>,
645 histogram_points, -- List of cutoff points for each of the
646 -- data buckets
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)
653 -- Init
654 local stats
655 if not noise then
656 return
658 local time1 = minetest.get_us_time()
660 local size_v = {
661 x = options.size,
662 y = options.size,
663 z = options.size,
666 local startpos = pos
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
672 y_max = 0
673 startpos.y = pos.y
674 endpos.y = pos.y
675 size_v.z = nil
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]
693 -- Init stats
694 stats = {}
695 stats.avg = 0
696 local sum_of_values = 0
697 stats.value_count = 0
699 stats.histogram = {}
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
709 local perlin_map
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})
716 else
717 perlin_map = perlin_map_object:get_3d_map(startpos)
721 local x_max, z_max
722 if stats_mode then
723 x_max = STATISTICS_ITERATIONS - 1
724 y_max = 0
725 z_max = 0
726 else
727 x_max = endpos.x - startpos.x
728 z_max = endpos.z - startpos.z
731 -- Main loop (time-critical!)
732 for x=0, x_max do
733 for y=0, y_max do
734 for z=0, z_max do
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
738 -- loop
740 -- Get Perlin value at current pos
741 local abspos
742 if not stats_mode then
743 abspos = {
744 x = startpos.x + x,
745 y = startpos.y + y,
746 z = startpos.z + z,
748 elseif stats_mode then
749 abspos = {
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)
757 local indexpos = {
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
764 local perlin_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
771 -- edges.
772 -- Ideally, for cleaner code, the precalculated map would
773 -- take this into account as well but this has not
774 -- been done yet.
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})
778 else
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
784 -- See above
785 perlin_value = noise:get_3d(abspos_get)
786 else
787 -- See above
788 perlin_value = perlin_map[indexpos.z][indexpos.y][indexpos.x]
790 else
791 error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
792 return
795 -- Statistics
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
806 -- Histogram
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
810 break
813 -- More stats
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
827 else
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)
837 -- Get vmanip index
838 local index = varea:indexp(abspos)
839 if not index then
840 return
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
848 else
849 vdata[index] = minetest.CONTENT_AIR
850 vdata2[index] = 0
852 else
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
856 else
857 vdata[index] = minetest.CONTENT_AIR
858 vdata2[index] = 0
865 -- Final stats
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")
882 return stats
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)
895 if not noise then
896 return false
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
910 else
911 center.y = mpos.y + options.size/2
913 center.z = mpos.z + options.size/2
914 minetest.add_particle({
915 pos = center,
916 expirationtime = 4,
917 size = 16,
918 texture = "perlin_explorer_new_noisechunk.png",
919 glow = minetest.LIGHT_MAX,
923 if stats then
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
926 else
927 minetest.log("error", "[perlin_explorer] Could not get stats!")
928 return false
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)
936 local msg
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)
941 if msg ~= false then
942 minetest.chat_send_player(player:get_player_name(), msg)
944 else
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)
955 if not user then
956 return
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"))
961 return
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)
967 loaded_areas = {}
968 seeder_reseed(user, reseed)
969 else
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))
988 return noiseparams
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),
1004 -------------------
1005 -- Chat commands --
1006 -------------------
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
1016 return false
1019 dimensions = tonumber(dimensions)
1020 sidelen = tonumber(sidelen)
1021 min = tonumber(min)
1022 mid = tonumber(mid)
1023 max = tonumber(max)
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
1055 loaded_areas = {}
1057 return true, S("Perlin map generation options set!")
1058 end,
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, ]+)")
1067 if not offset then
1068 offset, scale, seed, sx, sy, sz, octaves, persistence, lacunarity = string.match(param, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)")
1069 if not offset then
1070 return false
1073 if not flags then
1074 flags = ""
1076 octaves = tonumber(octaves)
1077 offset = tonumber(offset)
1078 sx = tonumber(sx)
1079 sy = tonumber(sy)
1080 sz = tonumber(sz)
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 = {
1088 octaves = octaves,
1089 offset = offset,
1090 scale = scale,
1091 spread = { x = sx, y = sy, z = sz },
1092 persistence = persistence,
1093 lacunarity = lacunarity,
1094 seed = seed,
1095 flags = flags,
1097 noiseparams = fix_noiseparams(noiseparams)
1098 local _, _, _, badwaves = analyze_noiseparams(noiseparams)
1099 if badwaves then
1100 return false, TEXT_ERROR_BAD_WAVELENGTH
1102 set_perlin_noise(noiseparams)
1103 loaded_areas = {}
1104 return true, S("Active Perlin noise parameters set!")
1105 end,
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]+)")
1114 if not x then
1115 return false
1117 x = tonumber(x)
1118 y = tonumber(y)
1119 z = tonumber(z)
1120 size = tonumber(size)
1121 if not x or not y or not z or not size then
1122 return false
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)
1131 opts.size = size
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!")
1136 return true, msg
1137 end,
1140 minetest.register_chatcommand("perlin_toggle_mapgen", {
1141 privs = { server = true },
1142 description = S("Toggle the automatic Perlin noise map generator"),
1143 params = "",
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
1150 loaded_areas = {}
1152 minetest.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options.autogen))
1153 local msg
1154 if current_options.autogen then
1155 msg = S("Mapgen enabled!")
1156 else
1157 msg = S("Mapgen disabled!")
1159 return true, msg
1160 end,
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)
1167 local buttons
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"))
1171 else
1172 buttons = "button[3.5,2.5;3,0.75;done;"..F(S("Return")).."]"
1175 local form = [[
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"))..[[]
1181 container[0.25,0.5]
1182 textarea[0,0;9,1.4;;]]..F(message)..[[;]
1183 container_end[]
1184 container_end[]
1185 ]]..buttons
1186 minetest.show_formspec(player:get_player_name(), "perlin_explorer:error", form)
1189 -- Stats loading screen
1190 local show_histogram_loading_formspec = function(player)
1191 local form = [[
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"))..[[]
1197 container[0.25,0.8]
1198 label[0,0;]]..F(S("Collecting data, please wait …"))..[[]
1199 container_end[]
1200 container_end[]
1202 minetest.show_formspec(player:get_player_name(), "perlin_explorer:histogram_loading", form)
1205 -- Stats screen
1206 local show_histogram_formspec = function(player, options, stats)
1207 local txt = ""
1208 local maxh = 6.0
1209 local histogram
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.."]"
1217 local hstart = 1
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
1223 local max_value = 0
1224 for h=hstart, HISTOGRAM_BUCKETS do
1225 local value = stats.histogram[h]
1226 if value > max_value then
1227 max_value = value
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)
1237 local x = h * 0.9
1238 local bar_height = math.max(maxh * bar_ratio, 0.001)
1239 local coords = x..","..maxh-bar_height..";0.8,"..bar_height
1240 local box = ""
1241 if count > 0 then
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
1249 if h <= 1 then
1250 min = ""
1251 else
1252 min = F(string.format("%.1f", stats.histogram_points[h-1]))
1254 if h >= HISTOGRAM_BUCKETS then
1255 max = ""
1256 else
1257 max = F(string.format("%.1f", stats.histogram_points[h]))
1260 box = box .. "label["..x..",7.0;"..max.."\n"..min.."]"
1262 local tt
1263 if h == 1 then
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]))
1267 else
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
1274 local vmin, vmax
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)
1278 else
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"
1289 local form = [[
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"))..[[]
1295 container[0.25,0.5]
1296 ]]..labels..[[
1297 container_end[]
1298 container[0,2.75]
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"))..[[]
1302 container[0.25,0.6]
1303 ]]..histogram..[[
1304 container_end[]
1305 container_end[]
1306 button[7.25,11.35;3,0.5;done;]]..F(S("Done"))..[[]
1307 container_end[]
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
1318 local strwave
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")
1324 else
1325 strwave = string.format("%.0f", waves_a[w])
1327 table.insert(stringified_waves, strwave)
1329 return table.concat(stringified_waves, ", ")
1331 local form = [[
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"))..[[]
1347 container_end[]
1348 --]]
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()
1361 local np
1362 if noiseparams then
1363 np = noiseparams
1364 else
1365 np = current_perlin.noiseparams
1367 if not profile_id then
1368 profile_id = 1
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 = "", "", ""
1374 if np.spread then
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)
1386 local sidelen
1387 if f_sidelen then
1388 sidelen = tostring(f_sidelen)
1389 else
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 = {}
1418 local counter = 1
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")).."]"
1443 local autogen_label
1444 local create_btn = ""
1445 local xyzsize = ""
1446 if current_options.autogen then
1447 autogen_label = S("Disable mapgen")
1448 else
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")).."]"
1452 xyzsize = [[
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]
1469 local form = [[
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"))..[[]
1475 container[0.0,0.5]
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"))..[[]
1479 ]]..delete_btn..[[
1480 container_end[]
1481 container[0.0,1.5]
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"))..[[]
1496 container_end[]
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"))..[[]
1514 container_end[]
1517 container[0.25,6.0]
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."))..[[]
1526 container_end[]
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"))..[[]
1539 ]]..xyzsize..[[
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]
1549 container_end[]
1552 container[0,10.95]
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”")).."]"..
1555 create_btn..[[
1556 button[6.60,0;3.15,1;toggle_autogen;]]..F(autogen_label)..[[]
1557 tooltip[toggle_autogen;]]..F(S("Toggle the automatic map generator"))..[[]
1558 container_end[]
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
1570 return
1573 -- Analysis window
1574 if formname == "perlin_explorer:analyze" or formname == "perlin_explorer:error" then
1575 if fields.done 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)
1582 return
1585 -- Histogram window
1586 if formname == "perlin_explorer:histogram" then
1587 if fields.done 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)
1593 return
1596 -- Creator window
1597 if formname ~= "perlin_explorer:creator" then
1598 return
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
1606 return
1607 elseif fields.defaults == "false" then
1608 formspec_states[player_name].defaults = false
1609 return
1611 if fields.eased == "true" then
1612 formspec_states[player_name].eased = true
1613 return
1614 elseif fields.eased == "false" then
1615 formspec_states[player_name].eased = false
1616 return
1618 if fields.absvalue == "true" then
1619 formspec_states[player_name].absvalue = true
1620 return
1621 elseif fields.absvalue == "false" then
1622 formspec_states[player_name].absvalue = false
1623 return
1626 -- Deleting a profile does not require any other field
1627 if fields.delete_np_profile then
1628 if #np_profiles <= 1 then
1629 return
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
1634 return
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)
1640 return
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
1648 else
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
1662 return
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 = {
1684 offset = offset,
1685 scale = scale,
1686 seed = seed,
1687 spread = spread,
1688 octaves = octaves,
1689 persistence = persistence,
1690 lacunarity = lacunarity,
1691 flags = build_flags_string(defaults, eased, absvalue),
1693 noiseparams = fix_noiseparams(noiseparams)
1694 -- Open analyze window
1695 if do_analyze then
1696 formspec_states[player_name].noiseparams = noiseparams
1697 analyze_noiseparams_and_show_formspec(player, noiseparams)
1698 return
1699 -- Change NP profile selection
1700 elseif fields.load_np_profile and fields.np_profiles then
1701 local profile = tonumber(fields.np_profiles)
1702 local loaded_np
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
1707 else
1708 -- Normal case: Read noiseparams from profile itself.
1709 loaded_np = np_profiles[profile].noiseparams
1711 -- Load new profile
1712 show_noise_formspec(player, loaded_np, profile)
1713 minetest.log("action", "[perlin_explorer] Loaded perlin noise profile "..profile)
1714 return
1715 -- Add new profile and save current noiseparams to it
1716 elseif fields.add_np_profile then
1717 table.insert(np_profiles, {
1718 np_type = "user",
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)
1725 return
1726 elseif fields.set_random_seed then
1727 -- Randomize seed
1728 local profile = tonumber(fields.np_profiles)
1729 noiseparams.seed = math.random(0, 2^32-1)
1730 show_noise_formspec(player, noiseparams, profile)
1731 return
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)
1740 return
1743 if not (dimensions and sidelen and value_min and value_mid and value_max and nodetype and buildmode) then
1744 return
1746 -- Convert dropdown index to actual dimensions number
1747 dimensions = dimensions + 1
1748 -- Spread is used differently in 2D
1749 if dimensions == 2 then
1750 spread.y = spread.z
1752 local _, _, _, badwaves = analyze_noiseparams(noiseparams)
1753 if badwaves then
1754 formspec_states[player_name].noiseparams = noiseparams
1755 show_error_formspec(player, TEXT_ERROR_BAD_WAVELENGTH, true)
1756 return
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)
1772 return
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
1776 ssize = 1000
1777 elseif dimensions == 3 and ssize < 100 then
1778 ssize = 100
1780 local start = -math.floor(ssize/2)
1782 local pos = vector.new(start, start, start)
1783 local noise = PerlinNoise(noiseparams)
1784 local options = {
1785 dimensions = dimensions,
1786 size = ssize,
1787 sidelen = sidelen,
1789 -- Show a loading formspec
1790 show_histogram_loading_formspec(player)
1791 -- This takes long
1792 local _, stats = create_perlin(pos, noise, noiseparams, options, true)
1793 if stats then
1794 -- Update the formspec to show the result
1795 show_histogram_formspec(player, options, stats)
1796 else
1797 minetest.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
1799 return
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
1815 loaded_areas = {}
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
1822 return
1824 if current_options.autogen then
1825 loaded_areas = {}
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)
1833 if msg then
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
1839 loaded_areas = {}
1840 elseif do_apply and not current_options.autogen then
1841 if not px or not py or not pz then
1842 return
1844 local place_pos = vector.new(px, py, pz)
1845 current_options.pos = place_pos
1850 end)
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)
1860 if not user then
1861 return
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"))
1866 return
1868 show_noise_formspec(user)
1869 end,
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
1876 -- are changed.
1877 -- This will generate so-called noisechunks, which are like
1878 -- Minetest mapchunks, except with respect to this mod only.
1879 local timer = 0
1880 minetest.register_globalstep(function(dtime)
1881 timer = timer + dtime
1882 if timer < AUTOBUILD_UPDATE_TIME then
1883 return
1885 timer = 0
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!")
1896 return
1898 if not pos_hash then
1899 minetest.log("error", "[perlin_explorer] build(): Invalid pos_hash!")
1900 return
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
1908 -- again
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
1918 local cc = c
1919 if current_options.dimensions == 2 then
1920 cc = 0
1922 for cx=-c, c do
1923 for cy=-cc, cc do
1924 for cz=-c, c do
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)
1935 if node ~= nil then
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)")
1945 -- Build the nodes!
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)")
1956 end)
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,
1967 sidelen = 1,
1968 dimensions = 2,
1969 noiseparams = table.copy(default_noiseparams),
1971 -- This is used by the `unique_formspec_spaces` hack
1972 sequence_number = 0,
1974 end)
1975 minetest.register_on_leaveplayer(function(player)
1976 local name = player:get_player_name()
1977 formspec_states[name] = nil
1978 end)