realterrain/init.lua

2471 lines
93 KiB
Lua

PROCESSOR = "native" -- options are: "native", "py", "gm", "magick", "imlib2"
print("PROCESSOR is "..PROCESSOR)
--imlib2 treats 16-bit as 8-bit and requires imlib2, magick requires magick wand -- magick is the most tested mode
--gm does not work and requires graphicksmagick, py is bit slow and requires lunatic-python to be built, and the PIL,
--CONVERT uses commandline imagemagick "convert" or graphicsmagick "gm CONVERT" ("CONVERT.exe" or "gm.exe CONVERT")
--native handles png, tiff, and bmp files but none currently only bmp works and only on 24 bit images with good headers
MODPATH = minetest.get_modpath("realterrain")
WORLDPATH = minetest.get_worldpath()
RASTERS = MODPATH .. "/rasters/"
SCHEMS = MODPATH .. "/schems/"
STRUCTURES = WORLDPATH .. "/structures/"
--make sure the structures folder is present
minetest.mkdir(STRUCTURES)
local ie = minetest.request_insecure_environment()
--ie.require "luarocks.loader" --if you use luarocks to install some of the packages below you may need this
package.path = (MODPATH.."/lib/lua-imagesize-1.2/?.lua;"..package.path)
local imagesize = ie.require "imagesize"
--[[package.path = (MODPATH.."/lib/luasocket/?.lua;"..MODPATH.."/lib/luasocket/?/init.lua;"..package.path)
local socket = ie.require "socket"--]]
local native, py, gm, magick, imlib2
if PROCESSOR == "py" then
package.loadlib("/usr/lib/x86_64-linux-gnu/libpython2.7.so", "*") --may not need to explicitly state this
package.path = (MODPATH.."/lib/lunatic-python-bugfix-1.1.1/?.lua;"..package.path)
py = ie.require("python", "*")
py.execute("import Image")
--py.execute("import numpy")
--py.execute("import grass.script as gscript")
py.execute("from osgeo import gdal")
py.execute("from gdalconst import *")
elseif PROCESSOR == "magick" then
package.path = (MODPATH.."/lib/magick/?.lua;"..MODPATH.."/lib/magick/?/init.lua;"..package.path)
magick = ie.require "magick"
MAGICK_AS_CONVERT = true --when false uses pixel-access, true uses enumeration-parsing (as GM does) (bit detection, slower)
elseif PROCESSOR == "imlib2" then
package.path = (MODPATH.."/lib/lua-imlib2/?.lua;"..package.path)
imlib2 = ie.require "imlib2"
elseif PROCESSOR == "gm" then
package.path = (MODPATH.."/lib/?.lua;"..MODPATH.."/lib/?/init.lua;"..package.path)
gm = ie.require "graphicsmagick"
elseif PROCESSOR == "convert" then
CONVERT = "gm convert" -- could also be CONVERT.exe, "gm CONVERT" or "gm.exe CONVERT"
elseif PROCESSOR == "native" then
dofile(MODPATH.."/lib/iohelpers.lua")
dofile(MODPATH.."/lib/imageloader.lua")
end
local realterrain = {}
realterrain.settings = {}
realterrain.validate = {}
--defaults
realterrain.settings.output = "normal"
realterrain.settings.yscale = 1
realterrain.validate.yscale = "number"
realterrain.settings.xscale = 1
realterrain.validate.xscale = "number"
realterrain.settings.zscale = 1
realterrain.validate.zscale = "number"
realterrain.settings.yoffset = 0
realterrain.validate.yoffset = "number"
realterrain.settings.xoffset = 0
realterrain.validate.xoffset = "number"
realterrain.settings.zoffset = 0
realterrain.validate.zoffset = "number"
realterrain.settings.waterlevel = 0
realterrain.validate.waterlevel = "number"
realterrain.settings.alpinelevel = 1000
realterrain.validate.alpinelevel = "number"
realterrain.settings.fileelev = 'dem.bmp'
realterrain.settings.elevbits = 8
realterrain.validate.elevbits = "number"
realterrain.settings.filecover = 'biomes.bmp'
realterrain.settings.coverbits = 8
realterrain.validate.coverbits = "number"
realterrain.settings.fileinput = ''
realterrain.settings.inputbits = 8
realterrain.validate.inputbits = "number"
realterrain.settings.fileinput2 = ''
realterrain.settings.input2bits = 8
realterrain.validate.input2bits = "number"
realterrain.settings.fileinput3 = ''
realterrain.settings.input3bits = 8
realterrain.validate.input3bits = "number"
realterrain.settings.dist_lim = 40
realterrain.validate.dist_lim = "number"
realterrain.settings.dist_mode = "3D" --3D or 3Dp
realterrain.settings.dist_to_min = 1
realterrain.validate.dist_to_min = "number"
realterrain.settings.dist_to_max = 255
realterrain.validate.dist_to_max = "number"
realterrain.settings.polya = 0.00001
realterrain.validate.polya = "number"
realterrain.settings.polyb = 0.001
realterrain.validate.polyb = "number"
realterrain.settings.polyc = 0.001
realterrain.validate.polyc = "number"
realterrain.settings.polyd = 0.01
realterrain.validate.polyd = "number"
realterrain.settings.polye = 0.01
realterrain.validate.polye = "number"
realterrain.settings.polyf = 0
realterrain.validate.polyf = "number"
realterrain.settings.polyg = 0
realterrain.validate.polyg = "number"
realterrain.settings.polyh = 0
realterrain.validate.polyh = "number"
--default cover (no cover)
realterrain.settings.b0ground = "default:dirt_with_dry_grass"
realterrain.settings.b0ground2 = "default:sand"
realterrain.settings.b0gprob = 10
realterrain.validate.b0gprob = "number"
realterrain.settings.b0tree = "tree"
realterrain.settings.b0tprob = 0.1
realterrain.validate.b0tprob = "number"
realterrain.settings.b0tree2 = "jungletree"
realterrain.settings.b0tprob2 = 30
realterrain.validate.v0tprob2 = "number"
realterrain.settings.b0shrub = "default:dry_grass_1"
realterrain.settings.b0sprob = 3
realterrain.validate.b0sprob = "number"
realterrain.settings.b0shrub2 = "default:dry_shrub"
realterrain.settings.b0sprob2 = 20
realterrain.validate.b0sprob2 = "number"
--USGS tier 1 landcover: 1 - URBAN or BUILT-UP
realterrain.settings.b1ground = "default:cobble"
realterrain.settings.b1ground2 = "default:cobble"
realterrain.settings.b1gprob = 0
realterrain.validate.b1gprob = "number"
realterrain.settings.b1tree = ""
realterrain.settings.b1tprob = 0
realterrain.validate.b1tprob = "number"
realterrain.settings.b1tree2 = ""
realterrain.settings.b1tprob2 = 0
realterrain.validate.b1tprob2 = "number"
realterrain.settings.b1shrub = "default:grass_1"
realterrain.settings.b1sprob = 0
realterrain.validate.b1sprob = "number"
realterrain.settings.b1shrub2 = "default:grass_1"
realterrain.settings.b1sprob2 = 0
realterrain.validate.b1sprob2 = "number"
--USGS tier 1 landcover: 2 - AGRICULTURAL
realterrain.settings.b2ground = "default:dirt_with_grass"
realterrain.settings.b2ground2 = "default:dirt_with_dry_grass"
realterrain.settings.b2gprob = 10
realterrain.validate.b2gprob = "number"
realterrain.settings.b2tree = ""
realterrain.settings.b2tprob = 0
realterrain.validate.b2tprob = "number"
realterrain.settings.b2tree2 = ""
realterrain.settings.b2tprob2 = 0
realterrain.validate.b2tprob = "number"
realterrain.settings.b2shrub = "default:grass_1"
realterrain.settings.b2sprob = 10
realterrain.validate.b2sprob = "number"
realterrain.settings.b2shrub2 = "default:dry_grass_1"
realterrain.settings.b2sprob2 = 50
realterrain.validate.b2sprob2 = "number"
--USGS tier 1 landcover: 3 - RANGELAND
realterrain.settings.b3ground = "default:dirt_with_grass"
realterrain.settings.b3ground2 = "default:dirt_with_dry_grass"
realterrain.settings.b3gprob = 30
realterrain.validate.b3gprob = "number"
realterrain.settings.b3tree = "tree"
realterrain.settings.b3tprob = 0.1
realterrain.validate.b3tprob = "number"
realterrain.settings.b3tree2 = "cactus"
realterrain.settings.b3tprob2 = 30
realterrain.validate.b3tprob2 = "number"
realterrain.settings.b3shrub = "default:dry_grass_1"
realterrain.settings.b3sprob = 5
realterrain.validate.b3sprob = "number"
realterrain.settings.b3shrub2 = "default:dry_shrub"
realterrain.settings.b3sprob2 = 50
realterrain.validate.b3sprob2 = "number"
--USGS tier 1 landcover: 4 - FOREST
realterrain.settings.b4ground = "default:dirt_with_grass"
realterrain.settings.b4ground2 = "default:gravel"
realterrain.settings.b4gprob = 10
realterrain.validate.b4gprob = "number"
realterrain.settings.b4tree = "jungletree"
realterrain.settings.b4tprob = 0.5
realterrain.validate.b4tprob = "number"
realterrain.settings.b4tree2 = "tree"
realterrain.settings.b4tprob2 = 30
realterrain.validate.b4tprob2 = "number"
realterrain.settings.b4shrub = "default:junglegrass"
realterrain.settings.b4sprob = 5
realterrain.validate.b4sprob = "number"
realterrain.settings.b4shrub2 = "default:grass_1"
realterrain.settings.b4sprob2 = 50
realterrain.validate.b4sprob2 = "number"
--USGS tier 1 landcover: 5 - WATER
realterrain.settings.b5ground = "realterrain:water_static" --not normal minetest water, too messy
realterrain.settings.b5ground2 = "realterrain:water_static"
realterrain.settings.b5gprob = 0
realterrain.validate.b5gprob = "number"
realterrain.settings.b5tree = ""
realterrain.settings.b5tprob = 0
realterrain.validate.b5tprob = "number"
realterrain.settings.b5tree2 = ""
realterrain.settings.b5tprob2 = 0
realterrain.validate.b5tprob2 = "number"
realterrain.settings.b5shrub = "default:grass_1"
realterrain.settings.b5sprob = 0
realterrain.validate.b5sprob = "number"
realterrain.settings.b5shrub2 = "default:grass_1"
realterrain.settings.b5sprob2 = 0
realterrain.validate.b5sprob2 = "number"
--USGS tier 1 landcover: 6 - WETLAND
realterrain.settings.b6ground = "default:dirt_with_grass" --@todo add a wetland node
realterrain.settings.b6ground2 = "realterrain:water_static"
realterrain.settings.b6gprob = 10
realterrain.validate.b6gprob = "number"
realterrain.settings.b6tree = ""
realterrain.settings.b6tprob = 0
realterrain.validate.b6tprob = "number"
realterrain.settings.b6tree2 = ""
realterrain.settings.b6tprob2 = 0
realterrain.validate.b6tprob2 = "number"
realterrain.settings.b6shrub = "default:junglegrass"
realterrain.settings.b6sprob = 20
realterrain.validate.b6sprob = "number"
realterrain.settings.b6shrub2 = "default:grass_1"
realterrain.settings.b6sprob2 = 40
realterrain.validate.b6sprob2 = "number"
--USGS tier 1 landcover: 7 - BARREN
realterrain.settings.b7ground = "default:sand"
realterrain.settings.b7ground2 = "default:dirt_with_dry_grass"
realterrain.settings.b7gprob = 10
realterrain.validate.b7gprob = "number"
realterrain.settings.b7tree = "cactus"
realterrain.settings.b7tprob = 0.2
realterrain.validate.b7tprob = "number"
realterrain.settings.b7tree2 = "tree"
realterrain.settings.b7tprob2 = 5
realterrain.validate.b7tprob2 = "number"
realterrain.settings.b7shrub = "default:dry_shrub"
realterrain.settings.b7sprob = 5
realterrain.validate.b7sprob = "number"
realterrain.settings.b7shrub2 = "default:dry_grass_1"
realterrain.settings.b7sprob2 = 50
realterrain.validate.b7sprob2 = "number"
--USGS tier 1 landcover: 8 - TUNDRA
realterrain.settings.b8ground = "default:gravel"
realterrain.settings.b8ground2 = "default:dirt_with_snow"
realterrain.settings.b8gprob = 10
realterrain.validate.b8gprob = "number"
realterrain.settings.b8tree = "snowtree"
realterrain.settings.b8tprob = 0.1
realterrain.validate.b8tprob = "number"
realterrain.settings.b8tree2 = "tree"
realterrain.settings.b8tprob2 = 5
realterrain.validate.b8tprob2 = "number"
realterrain.settings.b8shrub = "default:dry_grass_1"
realterrain.settings.b8sprob = 5
realterrain.validate.b8sprob = "number"
realterrain.settings.b8shrub2 = "default:dry_shrub"
realterrain.settings.b8sprob2 = 50
realterrain.validate.b8sprob2 = "number"
--USGS tier 1 landcover: PERENNIAL SNOW OR ICE
realterrain.settings.b9ground = "default:dirt_with_snow"
realterrain.settings.b9ground2 = "default:ice"
realterrain.settings.b9gprob = 10
realterrain.validate.b9gprob = "number"
realterrain.settings.b9tree = ""
realterrain.settings.b9tprob = 0
realterrain.validate.b9tprob = "number"
realterrain.settings.b9tree2 = ""
realterrain.settings.b9tprob2 = 0
realterrain.validate.b9tprob2 = "number"
realterrain.settings.b9shrub = "default:dry_grass_1"
realterrain.settings.b9sprob = 2
realterrain.validate.b9sprob = "number"
realterrain.settings.b9shrub2 = "default:dry_shrub"
realterrain.settings.b9sprob2 = 50
realterrain.validate.b9sprob2 = "number"
realterrain.neighborhood = {}
realterrain.neighborhood.a = {x= 1,y= 0,z= 1} -- NW
realterrain.neighborhood.b = {x= 0,y= 0,z= 1} -- N
realterrain.neighborhood.c = {x= 1,y= 0,z= 1} -- NE
realterrain.neighborhood.d = {x=-1,y= 0,z= 0} -- W
--neighborhood.e = {x= 0,y= 0,z= 0} -- SELF
realterrain.neighborhood.f = {x= 1,y= 0,z= 0} -- E
realterrain.neighborhood.g = {x=-1,y= 0,z=-1} -- SW
realterrain.neighborhood.h = {x= 0,y= 0,z=-1} -- S
realterrain.neighborhood.i = {x= 1,y= 0,z=-1} -- SE
local slopecolors = {"00f700", "5af700", "8cf700", "b5f700", "def700", "f7de00", "ffb500", "ff8400","ff4a00", "f70000"}
for k, colorcode in next, slopecolors do
minetest.register_node(
'realterrain:slope'..k, {
description = "Slope: "..k,
tiles = { colorcode..'.bmp' },
light_source = 9,
groups = {oddly_breakable_by_hand=1, not_in_creative_inventory=1},
--[[after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Gis:"..colorcode);
meta:set_int("placed", os.clock()*1000);
end,--]]
})
end
--register the aspect symbology nodes
local aspectcolors = {"ff0000","ffa600","ffff00","00ff00","00ffff","00a6ff","0000ff","ff00ff"}
for k,colorcode in next, aspectcolors do
minetest.register_node(
'realterrain:aspect'..k, {
description = "Aspect: "..k,
tiles = { colorcode..'.bmp' },
light_source = 9,
groups = {oddly_breakable_by_hand=1, not_in_creative_inventory=1},
--[[after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Gis:"..colorcode);
meta:set_int("placed", os.clock()*1000);
end,--]]
})
end
local websafe = {"00","33","66","99","cc","ff"}
local symbols = {}
for k,u in next, websafe do
for k,v in next, websafe do
for k,w in next, websafe do
table.insert(symbols, u..v..w)
end
end
end
for k,v in next, symbols do
minetest.register_node(
'realterrain:'..v, {
description = "Symbol: "..v,
tiles = { "white.bmp^[colorize:#"..v },
light_source = 9,
groups = {oddly_breakable_by_hand=1, not_in_creative_inventory=1},
--[[after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Gis:"..colorcode);
meta:set_int("placed", os.clock()*1000);
end,--]]
})
end
realterrain.settings.rastsymbol1 = "realterrain:slope1"
realterrain.settings.rastsymbol2 = "realterrain:slope2"
realterrain.settings.rastsymbol3 = "realterrain:slope3"
realterrain.settings.rastsymbol4 = "realterrain:slope4"
realterrain.settings.rastsymbol5 = "realterrain:slope5"
realterrain.settings.rastsymbol6 = "realterrain:slope6"
realterrain.settings.rastsymbol7 = "realterrain:slope7"
realterrain.settings.rastsymbol8 = "realterrain:slope8"
realterrain.settings.rastsymbol9 = "realterrain:slope9"
realterrain.settings.rastsymbol10 = "realterrain:slope10"
minetest.register_node(
'realterrain:water_static', {
description = "Water that Stays Put",
tiles = { 'water_static.png' },
--light_source = 9,
groups = {oddly_breakable_by_hand=1},
sunlight_propagates = true,
--drawtype = "glasslike_framed_optional",
post_effect_color = { r=0, g=0, b=128, a=128 },
--[[after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Gis:"..colorcode);
meta:set_int("placed", os.clock()*1000);
end,--]]
})
realterrain.cids = nil
function realterrain.build_cids()
local cids = {}
--turn various content ids into variables for speed
cids["dirt"] = minetest.get_content_id("default:dirt")
cids["stone"] = minetest.get_content_id("default:stone")
cids["alpine"] = minetest.get_content_id("default:gravel")
cids["water_bottom"] = minetest.get_content_id("default:sand")
cids["water"] = minetest.get_content_id("water_source")
cids["air"] = minetest.get_content_id("air")
cids["lava"] = minetest.get_content_id("lava_source")
cids[0] = {ground=minetest.get_content_id(realterrain.settings.b0ground),
ground2=minetest.get_content_id(realterrain.settings.b0ground2),
shrub=minetest.get_content_id(realterrain.settings.b0shrub),
shrub2=minetest.get_content_id(realterrain.settings.b0shrub2)}
cids[1] = {ground=minetest.get_content_id(realterrain.settings.b1ground),
ground2=minetest.get_content_id(realterrain.settings.b1ground2),
shrub=minetest.get_content_id(realterrain.settings.b1shrub),
shrub2=minetest.get_content_id(realterrain.settings.b1shrub2)}
cids[2] = {ground=minetest.get_content_id(realterrain.settings.b2ground),
ground2=minetest.get_content_id(realterrain.settings.b2ground2),
shrub=minetest.get_content_id(realterrain.settings.b2shrub),
shrub2=minetest.get_content_id(realterrain.settings.b2shrub2)}
cids[3] = {ground=minetest.get_content_id(realterrain.settings.b3ground),
ground2=minetest.get_content_id(realterrain.settings.b3ground2),
shrub=minetest.get_content_id(realterrain.settings.b3shrub),
shrub2=minetest.get_content_id(realterrain.settings.b3shrub2)}
cids[4] = {ground=minetest.get_content_id(realterrain.settings.b4ground),
ground2=minetest.get_content_id(realterrain.settings.b4ground2),
shrub=minetest.get_content_id(realterrain.settings.b4shrub),
shrub2=minetest.get_content_id(realterrain.settings.b4shrub2)}
cids[5] = {ground=minetest.get_content_id(realterrain.settings.b5ground),
ground2=minetest.get_content_id(realterrain.settings.b5ground2),
shrub=minetest.get_content_id(realterrain.settings.b5shrub),
shrub2=minetest.get_content_id(realterrain.settings.b5shrub2)}
cids[6] = {ground=minetest.get_content_id(realterrain.settings.b6ground),
ground2=minetest.get_content_id(realterrain.settings.b6ground2),
shrub=minetest.get_content_id(realterrain.settings.b6shrub),
shrub2=minetest.get_content_id(realterrain.settings.b6shrub2)}
cids[7] = {ground=minetest.get_content_id(realterrain.settings.b7ground),
ground2=minetest.get_content_id(realterrain.settings.b7ground2),
shrub=minetest.get_content_id(realterrain.settings.b7shrub),
shrub2=minetest.get_content_id(realterrain.settings.b7shrub2)}
cids[8] = {ground=minetest.get_content_id(realterrain.settings.b8ground),
ground2=minetest.get_content_id(realterrain.settings.b8ground2),
shrub=minetest.get_content_id(realterrain.settings.b8shrub),
shrub2=minetest.get_content_id(realterrain.settings.b8shrub2)}
cids[9] = {ground=minetest.get_content_id(realterrain.settings.b9ground),
ground2=minetest.get_content_id(realterrain.settings.b9ground2),
shrub=minetest.get_content_id(realterrain.settings.b9shrub),
shrub2=minetest.get_content_id(realterrain.settings.b9shrub2)}
--register cids for SLOPE mode.name
for i=1,10 do
cids["symbol"..i] = minetest.get_content_id(realterrain.settings["rastsymbol"..i])
end
--register cids for ASPECT mode.name
for k, code in next, aspectcolors do
cids["aspect"..k] = minetest.get_content_id("realterrain:".."aspect"..k)
end
cids["symbol10"] = minetest.get_content_id("realterrain:slope10")
for k,v in next, symbols do
cids[v] = minetest.get_content_id("realterrain:"..v)
end
realterrain.cids = cids
end
--called at each form submission
function realterrain.save_settings()
local file = io.open(WORLDPATH.."/realterrain_settings", "w")
if file then
for k,v in next, realterrain.settings do
local line = {key=k, values=v}
file:write(minetest.serialize(line).."\n")
end
file:close()
end
end
-- load settings run at EOF at mod start
function realterrain.load_settings()
local file = io.open(WORLDPATH.."/realterrain_settings", "r")
if file then
for line in file:lines() do
if line ~= "" then
local tline = minetest.deserialize(line)
realterrain.settings[tline.key] = tline.values
end
end
file:close()
end
end
--retrieve individual form field --@todo haven't been using this much, been accesing the settings table directly
function realterrain.get_setting(setting)
if next(realterrain.settings) ~= nil then
if realterrain.settings[setting] then
if realterrain.settings[setting] ~= "" then
return realterrain.settings[setting]
else
return false
end
else
return false
end
else
return false
end
end
--read from file, various persisted settings
realterrain.load_settings()
--modes table for easier feature addition, fillbelow and moving_window require a buffer of at least 1
realterrain.modes = {}
table.insert(realterrain.modes, {name="normal", get_cover=true})
table.insert(realterrain.modes, {name="surface", get_cover=true, buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="elevation", buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="slope", buffer=1, fill_below=true, moving_window=true})
table.insert(realterrain.modes, {name="aspect", buffer=1, fill_below=true, moving_window=true})
table.insert(realterrain.modes, {name="curvature", buffer=1, fill_below=true, moving_window=true})
table.insert(realterrain.modes, {name="distance", get_input=true, buffer=realterrain.settings.dist_lim, fill_below=true})
table.insert(realterrain.modes, {name="elevchange", get_cover=true, get_input=true, buffer=1, fill_below=true })
table.insert(realterrain.modes, {name="coverchange", get_cover=true, get_input=true, buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="imageoverlay", get_input=true, get_input_color=true, buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="bandoverlay", get_input=true, get_input2=true, get_input3=true, buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="mandelbrot", computed=true, buffer=1, fill_below=true})
table.insert(realterrain.modes, {name="polynomial", computed=true, buffer=1, fill_below=true})
function realterrain.get_mode_idx(modename)
for k,v in next, realterrain.modes do
if v.name == modename then
return k
end
end
end
function realterrain.get_mode()
return realterrain.modes[realterrain.get_mode_idx(realterrain.settings.output)]
end
--need to override the minetest.formspec_escape to return empty string when nil
function realterrain.esc(str)
if not str or str == "" then return "" else return minetest.formspec_escape(str) end
end
function realterrain.list_images()
local list = {}
if package.config:sub(1,1) == "/" then
--Unix
--Loop through all files
for file in io.popen('find "'..RASTERS..'" -type f'):lines() do
local filename = string.sub(file, #RASTERS + 1)
local im = imagesize.imgsize(RASTERS .. filename)
if im then
table.insert(list, filename)
end
im = nil
end
return list
else
--Windows
--Open directory look for files, loop through all files
for filename in io.popen('dir "'..RASTERS..'" /b'):lines() do
local im = imagesize.imgsize(RASTERS .. filename)
if im then
table.insert(list, filename)
end
im = nil
end
return list
end
end
function realterrain.list_schems()
local list = {}
if package.config:sub(1,1) == "/" then
--Unix
--Loop through all files
for file in io.popen('find "'..SCHEMS..'" -type f'):lines() do
local filename = string.sub(file, #SCHEMS + 1)
if string.find(file, ".mts", -4) ~= nil then
table.insert(list, string.sub(filename, 1, -5))
end
end
return list
else
--Windows
--Open directory look for files, loop through all files
for filename in io.popen('dir "'..SCHEMS..'" /b'):lines() do
if string.find(filename, ".mts", -4) ~= nil then
table.insert(list, string.sub(filename, 1, -5))
end
end
return list
end
end
function realterrain.list_nodes()
local list = {}
--generate a list of all registered nodes that are simple blocks
for k,v in next, minetest.registered_nodes do
if v.drawtype == "normal" and string.sub(k, 1, 12) ~= "realterrain:" then
table.insert(list, k)
end
end
--add water and lava
table.insert(list, "realterrain:water_static")
table.insert(list, "default:water_source")
table.insert(list, "default:lava_source")
return list
end
function realterrain.list_plants()
local list = {}
--generate a list of all registered nodes that are simple blocks
for k,v in next, minetest.registered_nodes do
if v.drawtype == "plantlike" and string.sub(k, 1, 8) ~= "vessels:" then
table.insert(list, k)
end
end
return list
end
function realterrain.list_symbology()
local list = {}
--generate a list of all registered nodes that are simple blocks
for k,v in next, minetest.registered_nodes do
if v.drawtype == "normal" and string.sub(k, 1, 12) == "realterrain:" then
table.insert(list, k)
end
end
return list
end
function realterrain.get_idx(haystack, needle)
--returns the image id or if the image is not found it returns zero
for k,v in next, haystack do
if v == needle then
return k
end
end
return 0
end
-- SELECT the mechanism for loading the image which is later uesed by get_pixel()
--@todo throw warning if image sizes do not match the elev size
realterrain.elev = {}
realterrain.cover = {}
realterrain.input = {}
realterrain.input2 = {}
realterrain.input3 = {}
function realterrain.init()
local mode = realterrain.get_mode()
if not mode.computed then
local imageload
if PROCESSOR == "gm" then imageload = gm.Image
elseif PROCESSOR == "magick" then imageload = magick.load_image
elseif PROCESSOR == "imlib2" then imageload = imlib2.image.load
end
local rasternames = {}
table.insert(rasternames, "elev")
if mode.get_cover then table.insert(rasternames, "cover") end
if mode.get_input then table.insert(rasternames, "input") end
if mode.get_input2 then table.insert(rasternames, "input2") end
if mode.get_input3 then table.insert(rasternames, "input3") end
for k,rastername in next, rasternames do
if realterrain.settings["file"..rastername] ~= "" then
if PROCESSOR == "native" then
--use imagesize to get the dimensions and header offset
local width, length, format = imagesize.imgsize(RASTERS..realterrain.settings["file"..rastername])
print(rastername..": format: "..format.." width: "..width.." length: "..length)
if string.sub(format, -3) == "bmp" or string.sub(format, -6) == "bitmap" then
dofile(MODPATH.."/lib/loader_bmp.lua")
local bitmap, e = imageloader.load(RASTERS..realterrain.settings["file"..rastername])
if e then print(e) end
realterrain[rastername].image = bitmap
realterrain[rastername].width = width
realterrain[rastername].length = length
realterrain[rastername].bits = realterrain.settings[rastername.."bits"]
realterrain[rastername].format = "bmp"
elseif format == "image/png" then
dofile(MODPATH.."/lib/loader_png.lua")
local bitmap, e = imageloader.load(RASTERS..realterrain.settings["file"..rastername])
if e then print(e) end
realterrain[rastername].image = bitmap
realterrain[rastername].width = width
realterrain[rastername].length = length
realterrain[rastername].format = "png"
elseif format == "image/tiff" then
local file = io.open(RASTERS..realterrain.settings["file"..rastername], "rb")
realterrain[rastername].image = file
realterrain[rastername].width = width
realterrain[rastername].length = length
realterrain[rastername].bits = realterrain.settings[rastername.."bits"]
realterrain[rastername].format = "tiff"
else
print("your file should be an uncompressed tiff, png or bmp")
end
elseif PROCESSOR == "convert" then
local width, length, format = imagesize.imgsize(RASTERS..realterrain.settings["file"..rastername])
realterrain[rastername].width = width
realterrain[rastername].length = length
elseif PROCESSOR == "py" then
--get metadata from the raster using GDAL
py.execute("dataset = gdal.Open( '"..RASTERS..realterrain.settings["file"..rastername].."', GA_ReadOnly )")
realterrain[rastername].driver_short = tostring(py.eval("dataset.GetDriver().ShortName"))
realterrain[rastername].driver_long = tostring(py.eval("dataset.GetDriver().LongName"))
realterrain[rastername].raster_x_size = tostring(py.eval("dataset.RasterXSize"))
realterrain[rastername].raster_y_size = tostring(py.eval("dataset.RasterYSize"))
realterrain[rastername].projection = tostring(py.eval("dataset.GetProjection()"))
--[[py.execute("geotransform = dataset.GetGeoTansform()")
realterrain[rastername].origin_x = tostring(py.eval("geotransform[0]") or "")
realterrain[rastername].origin_y = tostring(py.eval("geotransform[3]") or "")
realterrain[rastername].pixel_x_size = tostring(py.eval("geotransform[1]") or "")
realterrain[rastername].pixel_y_size = tostring(py.eval("geotransform[5]") or "")--]]
print("driver short name: "..realterrain[rastername].driver_short)
print("driver long name: "..realterrain[rastername].driver_long)
print("size: "..realterrain[rastername].raster_x_size.."x"..realterrain[rastername].raster_y_size)
print("projection: "..realterrain[rastername].projection)
--print("origin: "..realterrain[rastername].origin_x.."x"..realterrain[rastername].origin_y)
--print("pixel size: "..realterrain[rastername].pixel_x_size.."x"..realterrain[rastername].pixel_y_size)
realterrain[rastername].metadata = tostring(py.eval("dataset.GetMetadata()"))
print(realterrain[rastername].metadata)
py.execute("dataset_band1 = dataset.GetRasterBand(1)")
realterrain[rastername].nodata = tostring(py.eval("dataset_band1.GetNoDataValue()"))
realterrain[rastername].min = tostring(py.eval("dataset_band1.GetMinimum()"))
realterrain[rastername].max = tostring(py.eval("dataset_band1.GetMaximum()"))
realterrain[rastername].scale = tostring(py.eval("dataset_band1.GetScale()"))
realterrain[rastername].unit = tostring(py.eval("dataset_band1.GetUnitType()"))
print("nodata: "..realterrain[rastername].nodata)
print("min: "..realterrain[rastername].min)
print("max: "..realterrain[rastername].max)
print("scale: "..realterrain[rastername].scale)
print("unit: "..realterrain[rastername].unit)
py.execute("dataset = None")
py.execute(rastername.." = Image.open('"..RASTERS..realterrain.settings["file"..rastername] .."')")
py.execute(rastername.."_w, "..rastername.."_l = "..rastername..".size")
realterrain[rastername].width = tonumber(tostring(py.eval(rastername.."_w")))
realterrain[rastername].length = tonumber(tostring(py.eval(rastername.."_l")))
realterrain[rastername].mode = tostring(py.eval(rastername..".mode"))
print(rastername.." mode: "..realterrain[rastername].mode)
--if we are doing a color overlay and the raster is a grayscale then CONVERT to color
--@todo this should only happen to the input raster
if mode.get_input_color and realterrain[rastername].mode ~= "RGB" then
py.execute(rastername.." = "..rastername..".convert('RGB')")
realterrain[rastername].mode = "RGB"
--if a color raster was supplied when a grayscale is needed, CONVERT to grayscale (L mode)
elseif not mode.get_input_color and realterrain[rastername].mode == "RGB" then
py.execute(rastername.." = "..rastername..".convert('L')")
realterrain[rastername].mode = "L"
end
py.execute(rastername.."_pixels = "..rastername..".load()")
else
realterrain[rastername].image = imageload(RASTERS..realterrain.settings["file"..rastername])
if realterrain[rastername].image then
if PROCESSOR == "gm" then
realterrain[rastername].width, realterrain[rastername].length = realterrain[rastername].image:size()
else--imagick or imlib2
realterrain[rastername].width = realterrain[rastername].image:get_width()
realterrain[rastername].length = realterrain[rastername].image:get_height()
if PROCESSOR == "magick" then
realterrain[rastername].bits = realterrain.settings[rastername.."bits"]
end
end
else
print("your "..rastername.." file is missing (should be: "..realterrain.settings["file"..rastername].."), maybe delete or edit world/realterrain_settings")
realterrain[rastername] = {}
end
end
print("["..PROCESSOR.."-"..rastername.."] file: "..realterrain.settings["file"..rastername].." width: "..realterrain[rastername].width..", length: "..realterrain[rastername].length)
else
print("no "..rastername.." selected")
realterrain[rastername] = {}
end
end
end
end
realterrain.surface_cache = {} --used to prevent reading of DEM for skyblocks
realterrain.fill_below_leftovers = {} --used to handle off-chunk draws
-- Set mapgen parameters
minetest.register_on_mapgen_init(function(mgparams)
minetest.set_mapgen_params({mgname="singlenode", flags="nolight"})
end)
-- On generated function
minetest.register_on_generated(function(minp, maxp, seed)
realterrain.generate(minp, maxp)
end)
function realterrain.generate(minp, maxp)
local t0 = os.clock()
local x1 = maxp.x
local y1 = maxp.y
local z1 = maxp.z
local x0 = minp.x
local y0 = minp.y
local z0 = minp.z
local treemap = {}
local fillmap = {}
--print("x0:"..x0..",y0:"..y0..",z0:"..z0..";x1:"..x1..",y1:"..y1..",z1:"..z1)
local sidelen = x1 - x0 + 1
local ystridevm = sidelen + 32
--calculate the chunk coordinates
local cx0 = math.floor((x0 + 32) / 80)
local cy0 = math.floor((y0 + 32) / 80)
local cz0 = math.floor((z0 + 32) / 80)
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
local data = vm:get_data()
local mode = realterrain.get_mode()
local modename = mode.name
--check to see if the current chunk is above (or below) the elevation range for this footprint
if realterrain.surface_cache[cz0] and realterrain.surface_cache[cz0][cx0] then
if realterrain.surface_cache[cz0][cx0].offelev then
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[OFF] "..chugent.." ms mapchunk ("..cx0..", "..cy0..", "..cz0..")")
return
end
if y0 >= realterrain.surface_cache[cz0][cx0].maxelev then
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[SKY] "..chugent.." ms mapchunk ("..cx0..", "..cy0..", "..cz0..")")
vm:set_data(data)
vm:calc_lighting()
vm:write_to_map(data)
vm:update_liquids()
return
end
if mode.name ~= "normal" and y1 <= realterrain.surface_cache[cz0][cx0].minelev then
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[SUB] "..chugent.." ms mapchunk ("..cx0..", "..cy0..", "..cz0..")")
return
end
end
local buffer = mode.buffer or 0
local computed = mode.computed
local get_cover = mode.get_cover
local get_input = mode.get_input
local get_input2 = mode.get_input2
local get_input3 = mode.get_input3
local get_input_color = mode.get_input_color
local fill_below = mode.fill_below
local moving_window = mode.moving_window
--build the heightmap and include different extents and values depending on mode
local heightmap = realterrain.build_heightmap(x0-buffer, x1+buffer, z0-buffer, z1+buffer)
--calculate the min and max elevations for skipping certain blocks completely
local minelev, maxelev
for z=z0, z1 do
for x=x0, x1 do
local elev
if heightmap[z] and heightmap[z][x] then
elev = heightmap[z][x].elev
if elev then
if not minelev then
minelev = elev
maxelev = elev
else
if elev < minelev then
minelev = elev
end
if elev > maxelev then
maxelev = elev
end
end
--when comparing two elevs we need both of their min/max elevs
if modename == "elevchange" then
local elev
elev = heightmap[z][x].input
if elev then
if not minelev then
minelev = elev
maxelev = elev
else
if elev < minelev then
minelev = elev
end
if elev > maxelev then
maxelev = elev
end
end
end
end
end
end
end
end
--making distance more efficient
local input_present = false
if modename == "distance" then
for z,v1 in next, heightmap do
for x,v2 in next, v1 do
if not input_present and heightmap[z][x].input and heightmap[z][x].input > 0 then
input_present = true
end
end
end
end
-- if there were elevations in this footprint then add the min and max to the cache table if not already there
if minelev then
--print("minelev: "..minelev..", maxelev: "..maxelev)
if not realterrain.surface_cache[cz0] then
realterrain.surface_cache[cz0] = {}
end
if not realterrain.surface_cache[cz0][cx0] then
realterrain.surface_cache[cz0][cx0] = {minelev = minelev, maxelev=maxelev}
end
else
--otherwise this chunk was off the DEM raster
if not realterrain.surface_cache[cz0] then
realterrain.surface_cache[cz0] = {}
end
if not realterrain.surface_cache[cz0][cx0] then
realterrain.surface_cache[cz0][cx0] = {offelev=true}
end
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[OFF] "..chugent.." ms mapchunk ("..cx0..", "..cy0..", "..cz0..")")
return
end
--print(dump(heightmap))
if not realterrain.cids then
realterrain.build_cids()
end
local cids = realterrain.cids
--print(dump(cids))
local c_dirt_with_grass = minetest.get_content_id("default:dirt_with_grass")
local c_dirt_with_dry_grass = minetest.get_content_id("default:dirt_with_dry_grass")
local c_dirt_with_snow = minetest.get_content_id("default:dirt_with_snow")
local c_stone = minetest.get_content_id("default:stone")
local c_dirt = minetest.get_content_id("default:dirt")
--generate!
for z = z0, z1 do
for x = x0, x1 do
if heightmap[z] and heightmap[z][x] and heightmap[z][x]["elev"] then
--get the height needed to fill_below in surface mode
local height
if fill_below then
height = realterrain.fill_below(x,z,heightmap)
end
if not computed then
--modes that use biomes:
if get_cover then
local elev = heightmap[z][x].elev -- elevation in meters from DEM and water true/false
local cover = heightmap[z][x].cover
local cover2, elev2
--print(cover)
if not cover or cover < 1 then
cover = 0
else
cover = tonumber(string.sub(tostring(cover),1,1))
end
if modename == "elevchange" then
elev2 = heightmap[z][x].input
end
if modename == "coverchange" then
cover2 = heightmap[z][x].input
if not cover2 or cover2 < 1 then
cover2 = 0
else
cover2 = tonumber(string.sub(tostring(cover2),1,1))
end
end
--print("elev: "..elev..", cover: "..cover)
local ground, ground2, gprob, tree, tprob, tree2, tprob2, shrub, sprob, shrub2, sprob2
ground = cids[cover].ground
ground2 = cids[cover].ground2
gprob = tonumber(realterrain.get_setting("b"..cover.."gprob"))
tree = realterrain.get_setting("b"..cover.."tree")
tprob = tonumber(realterrain.get_setting("b"..cover.."tprob"))
tree2 = realterrain.get_setting("b"..cover.."tree2")
tprob2 = tonumber(realterrain.get_setting("b"..cover.."tprob2"))
shrub = cids[cover].shrub
sprob = tonumber(realterrain.get_setting("b"..cover.."sprob"))
shrub2 =cids[cover].shrub2
sprob2 = tonumber(realterrain.get_setting("b"..cover.."sprob2"))
--[[if tree then print("cover: "..cover..", ground: "..ground..", tree: "..tree..", tprob: "..tprob..", shrub: "..shrub..", sprob: "..sprob)
else print("cover: "..cover..", ground: "..ground..", tprob: "..tprob..", shrub: "..shrub..", sprob: "..sprob)
end]]
local vi = area:index(x, y0, z) -- voxelmanip index
for y = y0, y1 do
--underground layers
if y < elev and (mode.name == "normal") then
--create strata of stone, cobble, gravel, sand, coal, iron ore, etc
if y < elev-(math.random(10,15)) then
data[vi] = c_stone
else
--dirt with grass and dry grass fix
if ( ground == c_dirt_with_grass or ground == c_dirt_with_dry_grass or ground == c_dirt_with_snow ) then
data[vi] = c_dirt
else
data[vi] = ground
end
end
--the surface layer, determined by cover value
elseif y == elev
and ( cover ~= 5 or modename == "elevchange" or modename == "coverchange" or modename =="surface")
or (y < elev and y >= (elev - height) and fill_below) then
if modename == "coverchange" and cover2 and cover ~= cover2 then
--print("cover1: "..cover..", cover2: "..cover2)
data[vi] = cids["symbol10"]
elseif modename == "elevchange" and elev2 and (elev ~= elev2) then
local diff = elev2 - elev
if diff < 0 then
color = "symbol10"
else
color = "symbol1"
end
data[vi] = cids[color]
elseif y < tonumber(realterrain.settings.waterlevel) then
data[vi] = cids["water_bottom"]
--alpine level
elseif y > tonumber(realterrain.settings.alpinelevel) + math.random(1,5) then
data[vi] = cids["alpine"]
--default
else
--print("ground2: "..ground2..", gprob: "..gprob)
if gprob and gprob > 0 and ground2 and math.random(0,100) <= gprob then
data[vi] = ground2
else
data[vi] = ground
end
if y < elev
and ( data[vi] == c_dirt_with_grass or data[vi] == c_dirt_with_dry_grass or data[vi] == c_dirt_with_snow ) then
data[vi] = cids["dirt"]
end
end
--shrubs and trees one block above the ground
elseif y == elev + 1 then
if sprob > 0 and shrub and math.random(0,100) <= sprob then
if sprob2 and sprob2 > 0 and shrub2 and math.random(0,100) <= sprob2 then
data[vi] = shrub2
else
data[vi] = shrub
end
elseif tprob > 0 and tree and y < tonumber(realterrain.settings.alpinelevel) + math.random(1,5) and math.random(0,100) <= tprob then
if tprob2 and tprob2 > 0 and tree2 and math.random(0,100) <= tprob2 then
table.insert(treemap, {pos={x=x,y=y,z=z}, type=tree2})
else
table.insert(treemap, {pos={x=x,y=y,z=z}, type=tree})
end
end
elseif y <= tonumber(realterrain.settings.waterlevel) then
data[vi] = cids["water"] --normal minetest water source
end
vi = vi + ystridevm
end --end y iteration
else --raster output mode implied if cover is not set
local vi = area:index(x, y0, z) -- voxelmanip index
for y = y0, y1 do
local elev
elev = heightmap[z][x].elev
if y == elev or (y < elev and y >= (elev - height) and fill_below) then
local neighbors = {}
local edge_case = false
--moving window mode.names need neighborhood built
if moving_window then
neighbors["e"] = y
for dir, offset in next, realterrain.neighborhood do
--get elev for all surrounding nodes
local nelev
if heightmap[z+offset.z] and heightmap[z+offset.z][x+offset.x]then
nelev = heightmap[z+offset.z][x+offset.x].elev
end
if nelev then
neighbors[dir] = nelev
else --edge case, need to abandon this pixel for slope
edge_case = true
--print("edgecase")
end
end
end
if not edge_case then
local color
if modename == "elevation" then
if elev < 10 then color = "symbol1"
elseif elev < 20 then color = "symbol2"
elseif elev < 50 then color = "symbol3"
elseif elev < 100 then color = "symbol4"
elseif elev < 150 then color = "symbol5"
elseif elev < 200 then color = "symbol6"
elseif elev < 300 then color = "symbol7"
elseif elev < 450 then color = "symbol8"
elseif elev < 600 then color = "symbol9"
elseif elev >= 600 then color = "symbol10" end
--print("elev: "..elev)
data[vi] = cids[color]
elseif modename == "slope" then
local slope = realterrain.get_slope(neighbors)
if slope < 1 then color = "symbol1"
elseif slope < 2 then color = "symbol2"
elseif slope < 5 then color = "symbol3"
elseif slope < 10 then color = "symbol4"
elseif slope < 15 then color = "symbol5"
elseif slope < 20 then color = "symbol6"
elseif slope < 30 then color = "symbol7"
elseif slope < 45 then color = "symbol8"
elseif slope < 60 then color = "symbol9"
elseif slope >= 60 then color = "symbol10" end
--print("slope: "..slope)
data[vi] = cids[color]
elseif modename == "aspect" then
local aspect = realterrain.get_aspect(neighbors)
local slice = 22.5
if aspect > 360 - slice or aspect <= slice then color = "aspect1"
elseif aspect <= slice * 3 then color = "aspect2"
elseif aspect <= slice * 5 then color = "aspect3"
elseif aspect <= slice * 7 then color = "aspect4"
elseif aspect <= slice * 9 then color = "aspect5"
elseif aspect <= slice * 11 then color = "aspect6"
elseif aspect <= slice * 13 then color = "aspect7"
elseif aspect <= slice * 15 then color = "aspect8" end
--print(aspect..":"..color)
data[vi] = cids[color]
elseif modename == "curvature" then
local curve = realterrain.get_curvature(neighbors)
--print("raw curvature: "..curve)
if curve < -4 then color = "symbol1"
elseif curve < -3 then color = "symbol2"
elseif curve < -2 then color = "symbol3"
elseif curve < -1 then color = "symbol4"
elseif curve < 0 then color = "symbol5"
elseif curve > 4 then color = "symbol10"
elseif curve > 3 then color = "symbol9"
elseif curve > 2 then color = "symbol8"
elseif curve > 1 then color = "symbol7"
elseif curve >= 0 then color = "symbol6" end
data[vi] = cids[color]
elseif modename == "distance" then
local limit = realterrain.settings.dist_lim
--if there is no input present in the full search extent skip
if input_present then
local distance = realterrain.get_distance(x,y,z, heightmap)
if distance < (limit/10) then color = "symbol1"
elseif distance < (limit/10)*2 then color = "symbol2"
elseif distance < (limit/10)*3 then color = "symbol3"
elseif distance < (limit/10)*4 then color = "symbol4"
elseif distance < (limit/10)*5 then color = "symbol5"
elseif distance < (limit/10)*6 then color = "symbol6"
elseif distance < (limit/10)*7 then color = "symbol7"
elseif distance < (limit/10)*8 then color = "symbol8"
elseif distance < (limit/10)*9 then color = "symbol9"
else color = "symbol10"
end
else
color = "symbol10"
end
data[vi] = cids[color]
elseif (modename == "imageoverlay" and heightmap[z][x].input)
or (mode.name == "bandoverlay" and heightmap[z][x].input and heightmap[z][x].input2 and heightmap[z][x].input3) then
local input = heightmap[z][x].input
local input2 = heightmap[z][x].input2
local input3 = heightmap[z][x].input3
local color1 = math.floor( ( input / 255 ) * 5 + 0.5) * 51
local color2 = math.floor( ( input2 / 255 ) * 5 + 0.5) * 51
local color3 = math.floor( ( input3 / 255 ) * 5 + 0.5) * 51
--print("r: "..color1..", g: "..color2..", b: "..color3)
color1 = string.format("%x", color1)
if color1 == "0" then color1 = "00" end
color2 = string.format("%x", color2)
if color2 == "0" then color2 = "00" end
color3 = string.format("%x", color3)
if color3 == "0" then color3 = "00" end
color = color1..color2..color3
data[vi] = cids[color]
end
end
end
vi = vi + ystridevm
end -- end y iteration
end --end mode options for non-computed modes
else --computed mode implied
local vi = area:index(x, y0, z) -- voxelmanip index
for y = y0, y1 do
local elev = heightmap[z][x].elev
-- print at y = 0 for now, if we change this then get_surface needs to be updated
if y == 0 and modename == "mandelbrot" then
if elev < 1 then color = "symbol1"
elseif elev < 2 then color = "symbol2"
elseif elev < 3 then color = "symbol3"
elseif elev < 5 then color = "symbol4"
elseif elev < 8 then color = "symbol5"
elseif elev < 13 then color = "symbol6"
elseif elev < 21 then color = "symbol7"
elseif elev < 34 then color = "symbol8"
elseif elev < 55 then color = "symbol9"
elseif elev < 256 then color = "symbol10"
else color = "000000"
end
data[vi] = cids[color]
elseif modename == "polynomial"
and (y == elev or (y < elev and y >= (elev - height) and fill_below) ) then
--dirt with cover fix
local ground = cids[0].ground
if y < elev
and ( ground == c_dirt_with_grass or ground == c_dirt_with_dry_grass or ground == c_dirt_with_snow ) then
data[vi] = cids["dirt"]
else
data[vi] = ground
end
end
vi = vi + ystridevm
end --end y iteration
end --end modes
end --end if pixel is in heightmap
end
end
-- public function made by the default mod, to register ores and blobs
if default then
if default.register_ores then
default.register_ores()
end
if default.register_blobs then
default.register_blobs()
end
end
vm:set_data(data)
minetest.generate_ores(vm, minp, maxp)
vm:calc_lighting()
vm:write_to_map(data)
vm:update_liquids()
--place all the trees (schems assumed to be 7x7 bases with tree in center)
for k,v in next, treemap do
minetest.place_schematic({x=v.pos.x-3,y=v.pos.y,z=v.pos.z-3}, SCHEMS..v.type..".mts", (math.floor(math.random(0,3)) * 90), nil, false)
end
--place all structures whose pmin are in this chunk
local structures = realterrain.get_structures_for_chunk(x0,y0,z0)
for k,v in next, structures do
minetest.place_schematic({x=v.x,y=v.y,z=v.z}, STRUCTURES..v.schemname..".mts")
end
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[GEN] "..chugent.." ms mapchunk ("..cx0..", "..cy0..", "..cz0..")")
end
--the raw get pixel method that uses the selected method and accounts for bit depth
function realterrain.get_raw_pixel(x,z, rastername) -- "rastername" is a string
--print("x: "..x.." z: "..z..", rastername: "..rastername)
local colstart, rowstart = 0,0
if PROCESSOR == "native" and realterrain[rastername].format == "bmp" then
x=x+1
z=z-1
colstart = 1
rowstart = -1
end
z = -z
local r,g,b
local width, length
width = realterrain[rastername].width
length = realterrain[rastername].length
--check to see if the image is even on the raster, otherwise skip
if width and length and ( x >= rowstart and x <= width ) and ( z >= colstart and z <= length ) then
--print(rastername..": x "..x..", z "..z)
if PROCESSOR == "native" then
if realterrain[rastername].format == "bmp" then
local bitmap = realterrain[rastername].image
local c
if bitmap.pixels[z] and bitmap.pixels[z][x] then
c = bitmap.pixels[z][x]
r = c.r
g = c.g
b = c.b
--print("r: ".. r..", g: "..g..", b: "..b)
end
elseif realterrain[rastername].format == "png" then
local bitmap = realterrain[rastername].image
local c
if bitmap.pixels[z] and bitmap.pixels[z][x] then
c = bitmap.pixels[z][x]
r = c.r
g = c.g
b = c.b
end
elseif realterrain[rastername].format == "tiff" then
local file = realterrain[rastername].image
if not file then
print("tiff mode problem retrieving file handle")
end
--print(file)
if x < 0 or z < 0 or x >= width or z >= length then return end
if realterrain[rastername].bits == 8 then
file:seek("set", ((z) * width) + x + 8)
r = file:read(1)
if r then
r = r:byte()
r = tonumber(r)
--print(r)
else
print(rastername..": nil value encountered at x: "..x..", z: "..z)
r = nil
end
else
file:seek("set", ((z) * width * 2) + (x*2) + 11082) -- + 11082 cleans up the dem16.tif raster,
local r1 = file:read(1)
local r2 = file:read(1)
if r1 and r2 then
r = tonumber(r1:byte()) + tonumber(r2:byte())*256 --might be *256 the wrong byte
--print(r)
else
print(rastername..": one of two bytes is nil")
end
end
end
elseif PROCESSOR == "py" then
if realterrain[rastername].mode == "RGB" then
py.execute(rastername.."_r, "..rastername.."_g,"..rastername.."_b = "..rastername.."_pixels["..x..", "..z.."]")
r = tonumber(tostring(py.eval(rastername.."_r")))
g = tonumber(tostring(py.eval(rastername.."_g")))
b = tonumber(tostring(py.eval(rastername.."_b")))
else
r = tonumber(tostring(py.eval(rastername.."_pixels["..x..","..z.."]"))) --no bit depth conversion required
end
--print(r)
else
if realterrain[rastername].image then
if PROCESSOR == "magick" then
r,g,b = realterrain[rastername].image:get_pixel(x, z) --@todo change when magick autodetects bit depth
--print(rastername.." raw r: "..r..", g: "..g..", b: "..b..", a: "..a)
r = math.floor(r * (2^realterrain[rastername].bits))
g = math.floor(g * (2^realterrain[rastername].bits))
b = math.floor(b * (2^realterrain[rastername].bits))
elseif PROCESSOR == "imlib2" then
r = realterrain[rastername].image:get_pixel(x, z).red
g = realterrain[rastername].image:get_pixel(x, z).green
b = realterrain[rastername].image:get_pixel(x, z).blue
end
end
end
--print (v)
return r,g,b
end
end
function realterrain.get_brot_pixel(x,z)
--taken from https://plus.maths.org/content/computing-mandelbrot-set
--Where do we want to center the brot?
local cx = realterrain.settings.xoffset
local cz = realterrain.settings.zoffset
--This is the "zoom" factor.
local xscale = realterrain.settings.xscale
local zscale = realterrain.settings.zscale
local limit = 4 --Divergence check value.
local lp = 0 --Convergence check value.
local a1,b1,a2,b2 --For calculating the iterations.
local ax,az --The actual position of (x,z) in relation to the Mandelbrot set.
--What is the *mathematical* value of this point?
ax=cx+x*xscale
az=cz+z*zscale
--And now for the magic formula!
a1=ax
b1=az
--The first condition is satisfied if we have convergence. The second is satisfied if we have divergence.
while (lp<=255) and ((a1*a1)+(b1*b1)<=limit) do
--Do one iteration
lp=lp+1
a2=a1*a1-b1*b1+ax
b2=2*a1*b1+az
--This is indeed the square of a+bi, done component-wise.
a1=a2
b1=b2
end
if lp > 256 then print(">256:"..lp) end
return lp
end
function realterrain.polynomial(x,z)
local a,b,c,d,e,f,g,h
a = realterrain.settings.polya
b = realterrain.settings.polyb
c = realterrain.settings.polyc
d = realterrain.settings.polyd
e = realterrain.settings.polye
f = realterrain.settings.polyf
g = realterrain.settings.polyg
h = realterrain.settings.polyh
local value = (a*(x^2)*(z^2))+(b*(x^2)*(z))+(c*(x)*(z^2))+(d*(x^2))+(e*(z^2))+(f*(x))+(g*(z))+h
--print(value)
return math.floor(value)
end
--this function parses a line of IM or GM pixel enumeration without any scaling or adjustment
function realterrain.parse_enumeration(line, get_rgb)
local value
if line then
--print("enumeration line: "..line)
--parse the output pixels
local firstcomma = string.find(line, ",")
--print("first comma: "..firstcomma)
local right = tonumber(string.sub(line, 1 , firstcomma - 1)) + 1
--print("right: "..right)
local firstcolon = string.find(line, ":")
--print("first colon: "..firstcolon)
local down = tonumber(string.sub(line, firstcomma + 1 , firstcolon - 1))
--print("down: "..down)
local secondcomma
local firstpercent = string.find(line, "%%")
-- if a percent is found then we know we are using IM convert and it is a 16bit value
if firstpercent then
value = tonumber(string.sub(line, firstcolon + 3, firstpercent -1))
--print("value: "..value)
value = value / 100 * (2^16)
else
secondcomma = string.find(line, ",", firstcolon)
value = tonumber(string.sub(line, firstcolon + 3, secondcomma -1))
end
--get the blue and green channel as well if requested
if get_rgb then
local r,g,b
r = value
--print("r: "..r)
local thirdcomma = string.find(line, ",", secondcomma+1)
local closeparenthesis = string.find(line, ")")
local percent_or_not = 1
if firstpercent then percent_or_not = 2 end
g = tonumber(string.sub(line, secondcomma+1, thirdcomma - percent_or_not))
--print("g: "..g)
b = tonumber(string.sub(line, thirdcomma+1, closeparenthesis - percent_or_not))
--print("b: "..b)
value = {r=r,g=g,b=b}
end
return value, right, down
else
--print("no line")
return false
end
end
function realterrain.get_enumeration(rastername, firstcol, width, firstrow, length)
--print(rastername)
local table_enum = {}
local enumeration
if PROCESSOR == "gm" then
enumeration = realterrain[rastername].image:clone():crop(width,length,firstcol,firstrow):format("txt"):toString()
table_enum = string.split(enumeration, "\n")
elseif PROCESSOR == "magick" then
local tmpimg
tmpimg = realterrain[rastername].image:clone()
tmpimg:crop(width,length,firstcol,firstrow)
tmpimg:set_format("txt")
enumeration = tmpimg:get_blob()
tmpimg:destroy()
table_enum = string.split(enumeration, "\n")
elseif PROCESSOR == "convert" then
local cmd = CONVERT..' "'..RASTERS..realterrain.settings["file"..rastername]..'"'..
' -crop '..width..'x'..length..'+'..firstcol..'+'..firstrow..' txt:-'
enumeration = io.popen(cmd)
--print(cmd)
for line in enumeration:lines() do
table.insert(table_enum, line)
end
end
return table_enum
end
--main function that builds a heightmap using the various processors' methods available
function realterrain.build_heightmap(x0, x1, z0, z1)
local mode = realterrain.get_mode()
local modename = mode.name
local heightmap = {}
local xscale = realterrain.settings.xscale
local zscale = realterrain.settings.zscale
local xoffset = realterrain.settings.xoffset
local zoffset = realterrain.settings.zoffset
local yscale = realterrain.settings.yscale
local yoffset = realterrain.settings.yoffset
local scaled_x0 = math.floor(x0/xscale+xoffset+0.5)
local scaled_x1 = math.floor(x1/xscale+xoffset+0.5)
local scaled_z0 = math.floor(z0/zscale+zoffset+0.5)
local scaled_z1 = math.floor(z1/zscale+zoffset+0.5)
if not mode.computed then
local rasternames = {}
if realterrain.settings.fileelev ~= "" then table.insert(rasternames, "elev") end
if mode.get_cover and realterrain.settings.filecover ~= "" then table.insert(rasternames, "cover") end
if mode.get_input and realterrain.settings.fileinput ~= "" then table.insert(rasternames, "input") end
if mode.get_input2 and realterrain.settings.fileinput2 ~= "" then table.insert(rasternames, "input2") end
if mode.get_input3 and realterrain.settings.fileinput3 ~= "" then table.insert(rasternames, "input3") end
for k,rastername in next, rasternames do
--see if we are even on the raster or that there is a raster
if( not realterrain.settings["file"..rastername]
or (scaled_x1 < 0)
or (scaled_x0 > realterrain[rastername].width)
or (scaled_z0 > 0)
or (-scaled_z1 > realterrain[rastername].length)) then
--print("off raster request: scaled_x0: "..scaled_x0.." scaled_x1: "..scaled_x1.." scaled_z0: "..scaled_z0.." scaled_z1: "..scaled_z1)
return heightmap
end
--processors that require enumeration parsing rather than pixel-access
if PROCESSOR == "gm"
or PROCESSOR == "convert"
or (PROCESSOR == "magick" and MAGICK_AS_CONVERT) then
local pixels = {}
--convert map pixels to raster pixels
local cropstartx = scaled_x0
local cropendx = scaled_x1
local cropstartz = -scaled_z1
local cropendz = -scaled_z0
local empty_cols = 0
local empty_rows = 0
--don't request pixels to the left or above the raster, count how many we were off if we were going to
if scaled_x0 < 0 then
empty_cols = - scaled_x0
cropstartx = 0
end
if scaled_z1 > 0 then
empty_rows = scaled_z1
cropstartz = 0
end
--don't request pixels beyond maxrows or maxcols in the raster --@todo this doesn't account for scaling, offsets
if scaled_x1 > realterrain[rastername].width then cropendx = realterrain[rastername].width end
if -scaled_z0 > realterrain[rastername].length then cropendz = realterrain[rastername].length end
local cropwidth = cropendx-cropstartx+1
local croplength = cropendz-cropstartz+1
--print(rastername..": offcrop cols: "..empty_cols..", rows: "..empty_rows)
--print(rastername.." request range: x:"..x0..","..x1.."; z:"..z0..","..z1)
--print(rastername.." request entries: "..(x1-x0+1)*(z1-z0+1))
local enumeration = realterrain.get_enumeration(rastername, cropstartx, cropwidth, cropstartz, croplength)
--print(dump(enumeration))
local entries = 0
local mincol, maxcol, minrow, maxrow
local firstline = true
--build the pixel table from the enumeration
for k,line in next, enumeration do
if firstline and (PROCESSOR == "magick" or (PROCESSOR == "convert" and string.sub(CONVERT, 1, 2) ~= "gm" )) then
firstline = false --first line is a header in IM but not GM
--and do nothing
else
entries = entries + 1
--print(entries .." :: " .. v)
local value, right, down
if rastername == "input" and mode.get_input_color then
value,right,down = realterrain.parse_enumeration(line, true)
else
value,right,down = realterrain.parse_enumeration(line)
end
-- for elevation layers apply vertical scale and offset
if rastername == "elev" then
value = math.floor((value * realterrain.settings.yscale) + realterrain.settings.yoffset)
end
--convert the cropped pixel row/column back to absolute map x,z
if not pixels[-down] then pixels[-down] = {} end
pixels[-down][right] = value
end-- firstline test
end--end for enumeration line
--now we have to build the heightmap from the pixel table
for z=z0, z1 do
for x=x0,x1 do
if not heightmap[z] then heightmap[z] = {} end
if not heightmap[z][x] then heightmap[z][x] = {} end
--here is the tricky part, requesting the correct pixel for this x,z map coordinate
local newz = math.floor(z/zscale+zoffset+0.5)-scaled_z1 + empty_rows
local newx = math.floor(x/xscale+xoffset+0.5)-scaled_x0 - empty_cols +1 --@todo should 1 be scaled?
if pixels[newz] and pixels[newz][newx] then
if rastername == "input" and mode.get_input_color then
heightmap[z][x]["input"] = pixels[newz][newx].r
heightmap[z][x]["input2"] = pixels[newz][newx].g
heightmap[z][x]["input3"] = pixels[newz][newx].b
else
heightmap[z][x][rastername] = pixels[newz][newx]
end
end
end
end
if entries > 0 then
--print(rastername.." result range: x:"..mincol..","..maxcol.."; z:"..minrow..","..maxrow)
end
--print(rastername.." result entries: "..entries)
else --processors that require pixel-access instead of enumeration parsing
--local colstart, colend, rowstart, rowend = scaled_x0,scaled_x1,scaled_z0,scaled_z1
local colstart, colend, rowstart, rowend = x0,x1,z0,z1
for z=rowstart,rowend do
if not heightmap[z] then heightmap[z] = {} end
for x=colstart,colend do
local scaled_x = math.floor(x/xscale+xoffset+0.5)
local scaled_z = math.floor(z/zscale+zoffset+0.5)
if not heightmap[z][x] then heightmap[z][x] = {} end
if rastername == "input" and mode.get_input_color then
heightmap[z][x]["input"], heightmap[z][x]["input2"], heightmap[z][x]["input3"]
= realterrain.get_raw_pixel(scaled_x,scaled_z, "input")
else
if rastername == "elev" or (modename == "elevchange" and rastername == "input") then
local value = realterrain.get_raw_pixel(scaled_x,scaled_z, "elev")
if value then
heightmap[z][x][rastername] = math.floor(value*yscale+yoffset+0.5)
end
else
heightmap[z][x][rastername] = realterrain.get_raw_pixel(scaled_x,scaled_z, rastername)
end
end
end
end
end --end processor decisions
end --end for rasternames
elseif mode.computed then
for z=z0,z1 do
if not heightmap[z] then heightmap[z] = {} end
for x=x0,x1 do
if not heightmap[z][x] then heightmap[z][x] = {} end
if modename == "mandelbrot" then
heightmap[z][x]["elev"] = realterrain.get_brot_pixel(x,z)
elseif modename == "polynomial" then
heightmap[z][x]["elev"] = realterrain.polynomial(x,z)
end
end
end
end --end if computed
return heightmap
end
--this funcion gets the hieght needed to fill below a node for surface-only modes
function realterrain.fill_below(x,z,heightmap)
local height = 0
local height_in_chunk = 0
local height_below_chunk = 0
local below_positions = {}
local elev = heightmap[z][x].elev
for dir, offset in next, realterrain.neighborhood do
--get elev for all surrounding nodes
if dir == "b" or dir == "d" or dir == "f" or dir == "h" then
if heightmap[z+offset.z] and heightmap[z+offset.z][x+offset.x] and heightmap[z+offset.z][x+offset.x].elev then
local nelev = heightmap[z+offset.z][x+offset.x].elev
-- if the neighboring height is more than one down, check if it is the furthest down
if elev > ( nelev) and height < (elev-nelev) then
height = elev - nelev
end
end
end
end
--print(height)
return height -1
end
function realterrain.get_slope(n, rad)
--print(dump(n))
local x_cellsize, z_cellsize = 1, 1
local rise_xrun = ((n.c + 2 * n.f + n.i) - (n.a + 2 * n.d + n.g)) / (8 * x_cellsize)
local rise_zrun = ((n.g + 2 * n.h + n.i) - (n.a + 2 * n.b + n.c)) / (8 * z_cellsize)
local rise_xzrun = math.sqrt( rise_xrun ^ 2 + rise_zrun ^ 2 )
if rad then return rise_xzrun end
local degrees = math.atan(rise_xzrun) * 180 / math.pi
return math.floor(degrees + 0.5)
end
function realterrain.get_aspect(n, rad)
local rise_xrun = ((n.c + 2 * n.f + n.i) - (n.a + 2 * n.d + n.g)) / 8
local rise_zrun = ((n.g + 2 * n.h + n.i) - (n.a + 2 * n.b + n.c)) / 8
local aspect
if rise_xrun ~= 0 then
aspect = math.atan2(rise_zrun, - rise_xrun) * 180 / math.pi
if aspect < 0 then aspect = 2 * math.pi + aspect end
else
if rise_zrun > 0 then aspect = math.pi / 2
elseif rise_zrun < 0 then aspect = 2 * math.pi - (math.pi/2)
else aspect = 0 -- @todo not sure if this is actually 0
end
end
if rad then return aspect
else
local cell
if aspect < 0 then cell = 90.0 - aspect
elseif aspect > 90.0 then
cell = 360.0 - aspect + 90.0
else
cell = 90.0 - aspect
end
return math.floor(cell + 0.5)
end
end
function realterrain.get_curvature(n)
local curve
--[[local A,B,C,D,E,F,G,H,I --terms for polynomial
A = ((n.a + n.c + n.g + n.i) / 4 - (n.b + n.d + n.f + n.h) / 2 + n.e) -- / L^4 (cell size)
B = ((n.a + n.c - n.g - n.i) /4 - (n.b - n.h) /2) -- / L^3
C = ((-n.a + n.c - n.g + n.i) /4 + (n.d - n.f) /2) -- / L^3--]]
local D = ((n.d + n.f) /2 - n.e) -- / L^2
local E = ((n.b + n.h) /2 - n.e) -- / L^2
--[[F = (-n.a + n.c + n.g - n.i) -- / 4L^2
G = (-n.d + n.f) -- / 2^L
H = (n.b - n.h) -- / 2^L
I = n.e--]]
curve = -2*(D + E) -- * 100
return curve
end
-- this is not tested with offsets and scales but should work
function realterrain.get_distance(x,y,z, heightmap)
local limit = realterrain.settings.dist_lim
local dist_mode = realterrain.settings.dist_mode
local shortest = limit
local to_min = realterrain.settings.dist_to_min
local to_max = realterrain.settings.dist_to_max
--print("min: "..to_min..", max: "..to_max)
--buid a square around the search pixel
local c=0
for j=z-limit, z+limit do
for i=x-limit, x+limit do
c = c +1
local v, e
if heightmap[j] and heightmap[j][i] and heightmap[j][i].input then
v = heightmap[j][i].input
if dist_mode == "3D" then
e = heightmap[j][i].elev
end
if v and v >= to_min and v <= to_max then
local distance
if dist_mode == "2D" then
distance = math.sqrt(((z-j)^2)+((x-i)^2))
elseif dist_mode == "3D" then
distance = math.sqrt(((z-j)^2)+((x-i)^2)+((y-e)^2))
end
--print("candidate: "..distance)
if distance < shortest then
shortest = distance
--print("shorter found: "..shortest)
end
end
end
end
end
--print(c)
--print("distance: "..shortest)
return shortest
end
--after the mapgen has run, this gets the surface level
function realterrain.get_surface(x,z)
local heightmap = realterrain.build_heightmap(x,x,z,z)
if heightmap[z] and heightmap[z][x] and heightmap[z][x]["elev"] then
return heightmap[z][x]["elev"]
end
end
minetest.register_on_joinplayer(function(player)
--give player privs and teleport to surface
local pname = player:get_player_name()
minetest.chat_send_player(pname, "you are using the "..PROCESSOR.." processor")
local privs = minetest.get_player_privs(pname)
privs.fly = true
privs.fast = true
privs.noclip = true
privs.time = true
privs.teleport = true
privs.worldedit = true
minetest.set_player_privs(pname, privs)
minetest.chat_send_player(pname, "you have been granted some privs, like fast, fly, noclip, time, teleport and worldedit")
local ppos = player:getpos()
local surface = realterrain.get_surface(math.floor(ppos.x+0.5), math.floor(ppos.z+0.5))
if surface then
player:setpos({x=ppos.x, y=surface+0.5, z=ppos.z})
minetest.chat_send_player(pname, "you have been moved to the surface")
end
return true
end)
-- the controller for changing map settings
minetest.register_tool("realterrain:remote" , {
description = "Realterrain Settings",
inventory_image = "remote.png",
--left-clicking the tool
on_use = function (itemstack, user, pointed_thing)
local pname = user:get_player_name()
realterrain.show_rc_form(pname)
end,
})
-- Processing the form from the RC
minetest.register_on_player_receive_fields(function(player, formname, fields)
if string.sub(formname, 1, 12) == "realterrain:" then
local wait = os.clock()
while os.clock() - wait < 0.05 do end --popups don't work without this
--print("form, "..formname.." submitted: "..dump(fields))
local pname = player:get_player_name()
--the popup form never has settings so process that first
if formname == "realterrain:invalidated" then
if fields.exit == "Main" then
realterrain.show_rc_form(pname)
elseif fields.exit == "Cover" then
realterrain.show_cover_form(pname)
elseif fields.exit == "Ores" then
realterrain.show_ores_form(pname)
elseif fields.exit == "Symbols" then
realterrain.show_symbology(pname)
end
return true
end
--the main form
if formname == "realterrain:rc_form" then
--buttons that don't close the form:
local ppos = player:getpos()
if fields.gotosurface then
local surface = realterrain.get_surface(ppos.x, ppos.z)
if surface then
player:setpos({x=ppos.x, y=surface+0.5, z=ppos.z})
--should refresh this form so that the position info updates
realterrain.show_rc_form(pname)
else
minetest.chat_send_player(pname, "surface is undetectable")
end
return true
elseif fields.resetday then
minetest.set_timeofday(0.25)
elseif fields.setpos1 then
realterrain.pos1 = {x=math.floor(ppos.x+0.5),y=math.floor(ppos.y+0.5),z=math.floor(ppos.z+0.5)}
minetest.chat_send_player(pname, "pos1 set to ("..realterrain.pos1.x..","..realterrain.pos1.y..","..realterrain.pos1.z..")")
realterrain.show_rc_form(pname)
return true
elseif fields.setpos2 then
realterrain.pos2 = {x=math.floor(ppos.x+0.5),y=math.floor(ppos.y+0.5),z=math.floor(ppos.z+0.5)}
minetest.chat_send_player(pname, "pos2 set to ("..realterrain.pos2.x..","..realterrain.pos2.y..","..realterrain.pos2.z..")")
realterrain.show_rc_form(pname)
return true
elseif fields.posreset then
realterrain.pos1 = nil
realterrain.pos2 = nil
realterrain.show_rc_form(pname)
return true
elseif fields.savestructure then
if realterrain.pos1 and realterrain.pos2 then --will always be since button not shown otherwise
realterrain.save_structure(realterrain.pos1,realterrain.pos2)
minetest.chat_send_player(pname, "structure persisted to file")
end
return true
end
--actual form submissions
if fields.output or fields.fileelev or fields.filecover or fields.fileinput
or fields.fileinput2 or fields.fileinput3 then
--check to see if the source rasters were changed, if so re-initialize
local old_output, old_elev, old_cover, old_input
old_output = realterrain.settings.output
old_elev = realterrain.settings.fileelev
old_cover = realterrain.settings.filecover
old_input = realterrain.settings.fileinput
-- @todo validation for mode and raster selection changes, or not
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the mapgen settings!")
if old_elev ~= realterrain.settings.fileelev
or old_cover ~= realterrain.settings.filecover
or old_input ~= realterrain.settings.fileinput then
realterrain.init()
end
if old_output ~= realterrain.settings.output then
--redisplay the form so that mode-specific stuff is shown/hidden
realterrain.show_rc_form(pname)
return true
end
elseif fields.exit then --Apply or any other button
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the mapgen settings!")
end
if fields.exit == "Delete" then --@todo use the popup form do display a confirmation dialog box
--kick all players and delete the map file
local players = minetest.get_connected_players()
for k, player in next, players do
minetest.kick_player(player:get_player_name(), "map.sqlite deleted by admin, reload level")
end
minetest.register_on_shutdown(function()
local wait = os.clock()
while os.clock() - wait < 1 do end --the following delete happens too fast otherwise @todo this doesn't help
os.remove(WORLDPATH.."/map.sqlite")
end)
return true
elseif fields.exit == "Biomes" then
realterrain.show_cover_form(pname)
return true
elseif fields.exit == "Ores" then
realterrain.show_ores_form(pname)
return true
elseif fields.exit == "Symbols" then
realterrain.show_symbology(pname)
return true
end
return true
end
--cover config form
if formname == "realterrain:cover_config" then
-- @todo validated all non dropdown fields (numbers as numbers)
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the biome settings!")
if fields.exit == "Apply" then
realterrain.show_rc_form(pname)
return true
elseif fields.ground then
local setting = "b"..fields.ground.."ground"
realterrain.show_item_images(pname, realterrain.list_nodes(), setting)
elseif fields.ground2 then
local setting = "b"..fields.ground2.."ground2"
realterrain.show_item_images(pname, realterrain.list_nodes(), setting)
elseif fields.shrub then
local setting = "b"..fields.shrub.."shrub"
realterrain.show_item_images(pname, realterrain.list_plants(), setting)
elseif fields.shrub2 then
local setting = "b"..fields.shrub2.."shrub2"
realterrain.show_item_images(pname, realterrain.list_plants(), setting)
end
return true
end
--item image selection form
if formname == "realterrain:image_items" then
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the biome settings!")
realterrain.show_cover_form(pname)
return true
end
--raster symbology selection form
if formname == "realterrain:symbology" then
-- @todo validated all non dropdown fields (numbers as numbers)
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the symbology settings!")
if fields.exit == "Apply" then
realterrain.show_rc_form(pname)
return true
elseif fields.rastsymbol then
local setting = "rastsymbol"..fields.rastsymbol
minetest.chat_send_player(pname, "please be patient while all symbols load")
realterrain.show_all_symbols(pname, realterrain.list_symbology(), setting)
end
return true
end
--symbology selection form
if formname == "realterrain:all_symbols" then
-- save form fields, if errors then show popup
local invalids = realterrain.validate_and_save(fields)
if invalids ~= false then
realterrain.show_invalidated(pname, formname, invalids)
return false
end
minetest.chat_send_player(pname, "You changed the symbology settings!")
realterrain.show_symbology(pname)
return true
end
return true
end
end)
function realterrain.validate_and_save(fields)
local errors
for k,v in next, fields do
if realterrain.validate[k] then
--print("field, "..k.." has a validation rule")
local rule = realterrain.validate[k]
if rule == "number" then
if tonumber(v) then
realterrain.settings[k] = tonumber(v)
else
if not errors then errors = {} end
table.insert(errors, k)
end
end
else
realterrain.settings[k] = v
end
end
--save to file
realterrain.save_settings()
if errors then
--print(dump(errors))
return errors
else
return false
end
end
-- show the main remote control form
function realterrain.show_rc_form(pname)
local player = minetest.get_player_by_name(pname)
local ppos = player:getpos()
local degree = player:get_look_yaw()*180/math.pi - 90
if degree < 0 then degree = degree + 360 end
local dir
if degree <= 45 or degree > 315 then dir = "North"
elseif degree <= 135 then dir = "West"
elseif degree <= 225 then dir = "South"
else dir = "South" end
local howfar = "unknown"
local surface = realterrain.get_surface(math.floor(ppos.x+0.5), math.floor(ppos.z+0.5))
local above_below = "unknown"
if surface then
howfar = math.floor(math.abs(ppos.y-surface))
if ppos.y < surface then
above_below = "above"
else
above_below = "below"
end
else
surface = "unknown"
end
local mode = realterrain.get_mode()
local modename = mode.name
local images = realterrain.list_images()
local f_images = ""
for k,v in next, images do
f_images = f_images .. v .. ","
end
local bits = {}
bits["8"] = "1"
bits["16"] = "2"
local dmode = {}
dmode["2D"] = "1"
dmode["3D"] = "2"
local f_modes = ""
for k,v in next, realterrain.modes do
if f_modes == "" then
f_modes = v.name
else
f_modes = f_modes .. "," .. v.name
end
end
--print("IMAGES in DEM folder: "..f_images)
local col = {0.5, 2.5, 6.4, 8, 9, 10, 11, 12, 13}
--form header
local f_header = "size[14,10]" ..
"button[0,0;3,1;gotosurface;Teleport to Surface]"..
"button[3,0;3,1;resetday;Reset Morning Sun]"..
"label[6,0;You are at x= "..math.floor(ppos.x)..
" y= "..math.floor(ppos.y).." z= "..math.floor(ppos.z).." and mostly facing "..dir.."]"..
"label[6,0.5;The surface is "..howfar.." blocks "..
above_below.." you at "..surface.."]"
--Scale settings
local f_settings = "label["..col[1]..",1.1;Raster Mode]"..
"dropdown["..col[2]..",1;4,1;output;"..f_modes..";"..
realterrain.get_mode_idx(realterrain.settings.output).."]"
if modename == "normal" or modename == "surface" then
local pos1 = "not set"
local pos2 = "not set"
if realterrain.pos1 then
pos1 = "("..realterrain.pos1.x..","..realterrain.pos1.y..","..realterrain.pos1.z..")"
end
if realterrain.pos2 then
pos2 = "("..realterrain.pos2.x..","..realterrain.pos2.y..","..realterrain.pos2.z..")"
end
f_settings = f_settings ..
"label["..col[4]..",4;Structure Optoins:]"..
"label["..col[4]..",4.5;pos1: "..pos1..", pos2: "..pos2.."]"..
"button["..col[4]..",5;1,1;setpos1;Pos1]" ..
"button["..col[5]..",5;1,1;setpos2;Pos2]" ..
"button["..col[6]..",5;1.5,1;posreset;Clear]"
if realterrain.pos1 and realterrain.pos2 then
f_settings = f_settings..
"button["..col[8]..",5;1.5,1;savestructure;Save]"
end
end
if modename ~= "polynomial" then
f_settings = f_settings ..
"label["..col[4]-.2 ..",2;Scales]"..
"label["..col[7]-.2 ..",2;Offsets]"..
"label["..col[4]..",2.5;Y]"..
"label["..col[5]..",2.5;X]"..
"label["..col[6]..",2.5;Z]"..
"field["..col[4]..",3.25;1,1;yscale;;"..
realterrain.esc(realterrain.get_setting("yscale")).."]" ..
"field["..col[5]..",3.25;1,1;xscale;;"..
realterrain.esc(realterrain.get_setting("xscale")).."]" ..
"field["..col[6]..",3.25;1,1;zscale;;"..
realterrain.esc(realterrain.get_setting("zscale")).."]" ..
"label["..col[7]..",2.5;Y]"..
"label["..col[8]..",2.5;X]"..
"label["..col[9]..",2.5;Z]"..
"field["..col[7]..",3.25;1,1;yoffset;;"..
realterrain.esc(realterrain.get_setting("yoffset")).."]" ..
"field["..col[8]..",3.25;1,1;xoffset;;"..
realterrain.esc(realterrain.get_setting("xoffset")).."]" ..
"field["..col[9]..",3.25;1,1;zoffset;;"..
realterrain.esc(realterrain.get_setting("zoffset")).."]"
end
if modename == "distance" then
f_settings = f_settings ..
"label["..col[4]..",4;Distance Options:]"..
"field["..col[4]..",5.25;1,1;dist_lim;limit;"..
realterrain.esc(realterrain.get_setting("dist_lim")).."]" ..
"label["..col[5]..",4.6;mode:]"..
"dropdown["..col[5]..",5.05;1,1;dist_mode;2D,3D;"..
dmode[realterrain.esc(realterrain.get_setting("dist_mode"))].."]"..
"field["..col[7]..",5.25;1,1;dist_to_min;to min;"..
realterrain.esc(realterrain.get_setting("dist_to_min")).."]" ..
"field["..col[8]..",5.25;1.5,1;dist_to_max;to max;"..
realterrain.esc(realterrain.get_setting("dist_to_max")).."]"
end
if modename == "polynomial" then
f_settings = f_settings ..
"label["..col[4]..",4;Polynomial Co-efficients]"..
"field["..col[4]..",5.25;2,1;polya;(a*(x^2)*(z^2));"..
realterrain.esc(realterrain.get_setting("polya")).."]" ..
"field["..col[6]..",5.25;2,1;polyb;+(b*(x^2)*(z));"..
realterrain.esc(realterrain.get_setting("polyb")).."]" ..
"field["..col[8]..",5.25;2,1;polyc;+(c*(x)*(z^2));"..
realterrain.esc(realterrain.get_setting("polyc")).."]" ..
"field["..col[4]..",6.25;2,1;polyd;+(d*(x^2));"..
realterrain.esc(realterrain.get_setting("polyd")).."]" ..
"field["..col[6]..",6.25;2,1;polye;+(e*(z^2));"..
realterrain.esc(realterrain.get_setting("polye")).."]"..
"field["..col[8]..",6.25;2,1;polyf;+(f*(x));"..
realterrain.esc(realterrain.get_setting("polyf")).."]"..
"field["..col[4]..",7.25;2,1;polyg;+(g*(z));"..
realterrain.esc(realterrain.get_setting("polyg")).."]"..
"field["..col[6]..",7.25;2,1;polyh;+h;"..
realterrain.esc(realterrain.get_setting("polyh")).."]"
end
if not mode.computed then
f_settings = f_settings ..
"label["..col[1]..",3.1;Elevation File]"..
"dropdown["..col[2]..",3;4,1;fileelev;"..f_images..";"..
realterrain.get_idx(images, realterrain.get_setting("fileelev")) .."]"
end
if mode.get_cover then
f_settings = f_settings ..
"label["..col[1]..",4.1;Biome File]"..
"dropdown["..col[2]..",4;4,1;filecover;"..f_images..";"..
realterrain.get_idx(images, realterrain.get_setting("filecover")) .."]"
end
if mode.get_input then
f_settings = f_settings ..
"label["..col[1]..",5.1;Input File 1 (R)]"..
"dropdown["..col[2]..",5;4,1;fileinput;"..f_images..";"..
realterrain.get_idx(images, realterrain.get_setting("fileinput")) .."]"
end
if mode.get_input2 then
f_settings = f_settings ..
"label["..col[1]..",6.1;Input File 2 (G)]"..
"dropdown["..col[2]..",6;4,1;fileinput2;"..f_images..";"..
realterrain.get_idx(images, realterrain.get_setting("fileinput2")) .."]"
end
if mode.get_input3 then
f_settings = f_settings ..
"label["..col[1]..",7.1;Input File 3 (B)]"..
"dropdown["..col[2]..",7;4,1;fileinput3;"..f_images..";"..
realterrain.get_idx(images, realterrain.get_setting("fileinput3")) .."]"
end
if not mode.computed
and PROCESSOR ~= "py"
and PROCESSOR ~="gm"
and PROCESSOR ~= "convert"
and not (PROCESSOR == "magick" and MAGICK_AS_CONVERT) then --these modes know the bits
f_settings = f_settings ..
"label["..col[3]+0.2 ..",2;Bits]"..
"dropdown["..col[3]..",3;1,1;elevbits;8,16;"..
bits[realterrain.esc(realterrain.get_setting("elevbits"))].."]"
if mode.get_cover then
f_settings = f_settings ..
"dropdown["..col[3]..",4;1,1;coverbits;8,16;"..
bits[realterrain.esc(realterrain.get_setting("coverbits"))].."]"
end
if mode.get_input then
f_settings = f_settings ..
"dropdown["..col[3]..",5;1,1;inputbits;8,16;"..
bits[realterrain.esc(realterrain.get_setting("inputbits"))].."]"
end
if mode.get_input2 then
f_settings = f_settings ..
"dropdown["..col[3]..",6;1,1;input2bits2;8,16;"..
bits[realterrain.esc(realterrain.get_setting("input2bits"))].."]"
end
if mode.get_input3 then
f_settings = f_settings ..
"dropdown["..col[3]..",7;1,1;input3bits;8,16;"..
bits[realterrain.esc(realterrain.get_setting("input3bits"))].."]"
end
end
if modename == "normal" or modename == "surface" then
f_settings = f_settings ..
"field[1,9;2,1;waterlevel;Water Level;"..
realterrain.esc(realterrain.get_setting("waterlevel")).."]"..
"field[3,9;2,1;alpinelevel;Alpine Level;"..
realterrain.esc(realterrain.get_setting("alpinelevel")).."]"
end
--Action buttons
local f_footer = "button_exit[8,8;2,1;exit;Biomes]"..
--[["button_exit[10,8;2,1;exit;Ores]"..--]]
"button_exit[12,8;2,1;exit;Symbols]"..
"label[5.5,9;Apply and]"..
"label[5.5,9.4;delete map:]"..
"button_exit[7.1,9;2,1;exit;Delete]"..
"label[10,9.25;Apply only: ]"..
"button_exit[11.5,9;2,1;exit;Apply]"
minetest.show_formspec(pname, "realterrain:rc_form",
f_header ..
f_settings ..
f_footer
)
return true
end
function realterrain.show_cover_form(pname)
local schems = realterrain.list_schems()
local f_schems = ""
for k,v in next, schems do
f_schems = f_schems .. v .. ","
end
local col= {0.01, 0.5,1.3,2.1, 3.5,5.5,6.5,8.5, 10,11,12,13, 12.5}
local f_header = "size[14,10]" ..
"button_exit["..col[13]..",9.5;1.5,1;exit;Apply]"..
--"label["..col[1]..",0.01;USGS Biome]"..
"label["..col[2]..",0.01;Ground 1,2]"..
"label["..col[4]..",0.01;Mix]"..
"label["..col[5]..",0.01;Tree]".."label["..col[6]..",0.01;Prob]"..
"label["..col[7]..",0.01;Tree2]".."label["..col[8]..",0.01;Mix]"..
"label["..col[9]..",0.01;Shrub]".."label["..col[10]..",0.01;Prob]"..
"label["..col[11]..",0.01;Shrub2]".."label["..col[12]..",0.01;Mix]"
local f_body = ""
for i=0,9,1 do
local h = (i +1) * 0.7
f_body = f_body ..
"label["..col[1]..","..h ..";"..i.."]"..
"item_image_button["..(col[2])..","..(h-0.2)..";0.8,0.8;"..realterrain.get_setting("b"..i.."ground")..";ground;"..i.."]"..
"item_image_button["..(col[3])..","..(h-0.2)..";0.8,0.8;"..
realterrain.get_setting("b"..i.."ground2")..";ground2;"..i.."]"..
"field["..(col[4]+0.2)..","..h ..";1,1;b"..i.."gprob;;"..
realterrain.esc(realterrain.get_setting("b"..i.."gprob")).."]"
f_body = f_body ..
"dropdown["..col[5]..","..(h-0.3) ..";2,1;b"..i.."tree;"..f_schems..";"..
realterrain.get_idx(schems, realterrain.get_setting("b"..i.."tree")) .."]" ..
"field["..(col[6]+0.2)..","..h ..";1,1;b"..i.."tprob;;"..
realterrain.esc(realterrain.get_setting("b"..i.."tprob")).."]" ..
"dropdown["..col[7]..","..(h-0.3) ..";2,1;b"..i.."tree2;"..f_schems..";"..
realterrain.get_idx(schems, realterrain.get_setting("b"..i.."tree2")) .."]" ..
"field["..(col[8]+0.2)..","..h ..";1,1;b"..i.."tprob2;;"..
realterrain.esc(realterrain.get_setting("b"..i.."tprob2")).."]"
f_body = f_body ..
"item_image_button["..(col[9])..","..(h-0.2)..";0.8,0.8;"..realterrain.get_setting("b"..i.."shrub")..";shrub;"..i.."]"..
"field["..col[10]..","..h ..";1,1;b"..i.."sprob;;"..
realterrain.esc(realterrain.get_setting("b"..i.."sprob")).."]"..
"item_image_button["..(col[11])..","..(h-0.2)..";0.8,0.8;"..realterrain.get_setting("b"..i.."shrub2")..";shrub2;"..i.."]"..
"field["..col[12]..","..h ..";1,1;b"..i.."sprob2;;"..
realterrain.esc(realterrain.get_setting("b"..i.."sprob2")).."]"
end
local f_notes = "label[1,8;Biome 1 - Roads, Biome2 - Agriculture, Biome3 - Rangeland]"..
"label[1,8.5;Biome 4 - Forest, Biome 5 - Water, Biome 6 - Wetlands]"..
"label[1,9;Biome 7 - Barren, Biome 8 - Tundra, Biome 9 - Glacial]"
minetest.show_formspec(pname, "realterrain:cover_config",
f_header .. f_body .. f_notes
)
return true
end
function realterrain.show_ores_form(pname)
local col= {0.01, 0.5,1.3,2.1, 3.5,5.5,6.5,8.5, 10,11,12,13, 12.5}
local f_header = "size[14,10]" ..
"button_exit["..col[13]..",9.5;1.5,1;exit;Apply]"
--"label["..col[1]..",0.01;USGS Biome]"..
local f_body = ""
for i=0,9,1 do
local h = (i +1) * 0.7
f_body = ""
end
local f_notes = ""
minetest.show_formspec(pname, "realterrain:ores_config",
f_header .. f_body .. f_notes
)
return true
end
function realterrain.show_symbology(pname)
local col= {0.01,2}
local f_header = "size[14,10]" ..
"button_exit[11,0.01;2,1;exit;Apply]"..
"label["..col[1]..",0.01;Symbol]"..
"label["..col[2]..",0.01;Node]"
local f_body = ""
for i=1,10 do
local h = (i +1) * 0.7
f_body = f_body ..
"label["..col[1]..","..h ..";"..i.."]"..
"item_image_button["..(col[2])..","..(h-0.2)..";0.8,0.8;"..realterrain.get_setting("rastsymbol"..i)..";rastsymbol;"..i.."]"
end
minetest.show_formspec(pname, "realterrain:symbology",
f_header..f_body
)
return true
end
function realterrain.show_item_images(pname, items, setting)
local f_images = ""
local i = 1
local j = 1
for k,v in next, items do
f_images = f_images .. "item_image_button["..i..","..j..";1,1;"..items[k]..";"..setting..";"..items[k].."]"
if i < 12 then
i = i + 1
else
i = 1
j = j + 1
end
end
local f_body = "size[14,10]" ..
"button_exit[12,0.01;2,1;exit;Cancel]"
--print(f_images)
minetest.show_formspec(pname, "realterrain:image_items",
f_body..f_images
)
return true
end
function realterrain.show_all_symbols(pname, items, setting)
local f_images = ""
local i = 1
local j = 1
for k,v in next, items do
f_images = f_images .. "item_image_button["..(i*0.6)..","..(j*0.6)..";0.6,0.6;"..items[k]..";"..setting..";"..items[k].."]"
if i < 16 then
i = i + 1
else
i = 1
j = j + 1
end
end
local f_body = "size[14,10]" ..
"button_exit[12,0.01;2,1;exit;Cancel]"
--print(f_images)
minetest.show_formspec(pname, "realterrain:all_symbols",
f_body..f_images
)
return true
end
-- this is the form-error popup
function realterrain.show_invalidated(pname, formname, fields)
local back, message
if formname == "realterrain:rc_form" then back = "Main"
elseif formname == "realterrain:cover_config" then back = "Cover"
elseif formname == "realterrain:ores_config" then back = "Ores"
elseif formname == "realterrain:symbology" then back = "Symbols"
end
for k,v in next, fields do
if not message then
message = "The following fields were invalid: "..v
else
message = message .. ", "..v
end
end
minetest.chat_send_player(pname, "Form error: ".. message)
minetest.show_formspec(pname, "realterrain:invalidated",
"size[10,8]" ..
"button_exit[1,1;2,1;exit;"..back.."]"..
"label[1,3;"..realterrain.esc(message).."]"
)
return true
end
function realterrain.get_structures_for_chunk(x0,y0,z0)
local structures = {}
--look in the structures folder and check each one to see if it is in the chunk
local list = {}
if package.config:sub(1,1) == "/" then
--Unix
--Loop through all files
for file in io.popen('find "'..STRUCTURES..'" -type f'):lines() do
local filename = string.sub(file, #STRUCTURES + 1)
if string.find(file, ".mts", -4) ~= nil then
table.insert(list, string.sub(filename, 1, -5))
end
end
else
--Windows
--Open directory look for files, loop through all files
for filename in io.popen('dir "'..STRUCTURES..'" /b'):lines() do
if string.find(filename, ".mts", -4) ~= nil then
table.insert(list, string.sub(filename, 1, -5))
end
end
end
for k,v in next, list do
local split = string.split(v,"_")
local xmin = tonumber(split[1])
local ymin = tonumber(split[2])
local zmin = tonumber(split[3])
--print("x0 "..x0..", y0 "..y0..", z0 "..z0..", xmin "..xmin..", ymin "..ymin..", zmin "..zmin)
if xmin >= x0 and xmin < x0 +80 and ymin >= y0 and ymin < y0 +80 and zmin >= z0 and zmin < z0 +80 then
print("structure found for this chunk")
table.insert(structures,{x=xmin,y=ymin,z=zmin,schemname=v})
end
end
return structures
end
function realterrain.save_structure(pos1,pos2)
--swap the max and mins until pos1 is the pmin and pos2 is the pmax
local xmin,ymin,zmin,xmax,ymax,zmax
if pos1.x < pos2.x then xmin = pos1.x else xmin = pos2.x end
if pos1.y < pos2.y then ymin = pos1.y else ymin = pos2.y end
if pos1.z < pos2.z then zmin = pos1.z else zmin = pos2.z end
if pos1.x > pos2.x then xmax = pos1.x else xmax = pos2.x end
if pos1.y > pos2.y then ymax = pos1.y else ymax = pos2.y end
if pos1.z > pos2.z then zmax = pos1.z else zmax = pos2.z end
pos1 = {x=xmin, y=ymin, z=zmin}
pos2 = {x=xmax, y=ymax, z=zmax}
if minetest.create_schematic(pos1, pos2, nil, STRUCTURES..pos1.x.."_"..pos1.y.."_"..pos1.z..".mts") then
return true
end
end
realterrain.init()
--minelev, maxelev = realterrain.get_elev_range()