ground level

For people working on the C++ code.
Post Reply
Smitje
Member
Posts: 22
Joined: Wed Nov 14, 2012 20:42

ground level

by Smitje » Post

@admin:
since it turned out not to be a bug, but the answers may be helpfull to moders, could this be moved to "Modding general" please?

Sorry for the inrtusion,
As I am not a c++ coder Im not sure this is the right place to put this but because I found something odd that I suspect may be a bug I will anyway. Please be free to refer me to the appropriate place.

I was researching how to find the ground level for use in voxelmanip for the placement of some things. In the below thread it appears dgm555 was having a simmilar problem.
viewtopic.php?f=9&t=9286&hilit=groundlevel

So I had a peek in the C++ code to see if it would be possible to redo the calculation of the groundlevel from the parameters and the perlin noise functions. I can't code C++ but I can read some of it.

The first thing I found was in mapgen.cpp
on line 922 it describes a function findGroundLevelFull, and then on 940 there is findGroundLevel. These simply check for the highest walkable node. I think it would be possible to do the same within my lua script. But my first question is are these exposed to the Lua API?

The problem with this approach is it is hard to tell if I landed on a tree or in a cave, so I kept on digging to try and find the ground height definition.

The second thing is in mapgen_v6.cpp
in the function baseTerrainLevelFromMap
on line 239 it says:

Code: Select all

int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
where I would expect:

Code: Select all

int index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
I could well be wrong but please check.

While looking through mapgen_v6 I allso noticed the functions :
getGroundLevelAtPoint, block_is_underground, find_ground_level_from_noise and getBiome.
Simmilar to my first question are these available in the Lua API? These would allso be usefull for lua mapgen purposes.
Or would it be possible (without a lot of work) to make them available?

Kind regards,
Smitje
Last edited by Smitje on Sat Sep 06, 2014 21:21, edited 2 times in total.

User avatar
paramat
Developer
Posts: 3700
Joined: Sun Oct 28, 2012 00:05
GitHub: paramat
IRC: paramat
Location: UK

Re: ground level (possible bug)

by paramat » Post

It's not a bug because it is repeated throughout, and mapgen v6 would be a mess if those were errors.
These are all 2D noises which are processed abstractly with variables x and y, in mapgen those x and y variables are mapped to x and z co-ordinates.

Smitje
Member
Posts: 22
Joined: Wed Nov 14, 2012 20:42

Re: ground level (possible bug)

by Smitje » Post

@paramat,
Thanks, I should have checked the rest of the module sorry. I got confused because of the world coordinates having y as vertical axis, and assumed the same convention was used here , but for 2d coordinates I guess x, y makes more sense.

@all
Anyone know if any of these functions are available in the Lua API?

User avatar
paramat
Developer
Posts: 3700
Joined: Sun Oct 28, 2012 00:05
GitHub: paramat
IRC: paramat
Location: UK

Re: ground level (possible bug)

by paramat » Post

https://github.com/minetest/minetest/bl ... .txt#L2126 line 2126
Not sure if all of these are available within mapgen v6, some may be v7 only.

Smitje
Member
Posts: 22
Joined: Wed Nov 14, 2012 20:42

Re: ground level (possible bug)

by Smitje » Post

Thanks once again.

I just tested it and:

Code: Select all

lastheightmap = minetest.get_mapgen_object("heightmap")
returns a table in v7 but nil in v6. :(

So height will have to be recalculated or I need to get v7 working with something more usefull than stone. Is this a matter of the right conf settings or is it more complicated?

cheers,
Smitje

User avatar
HeroOfTheWinds
Member
Posts: 470
Joined: Wed Apr 23, 2014 23:16
GitHub: HeroOfTheWinds
IRC: WindHero
Location: Hawaii

Re: ground level (possible bug)

by HeroOfTheWinds » Post

To make v7 have biomes that are more useful than stone, there are two options:
Use the on_generated approach (such as in paragenv7), or
use the biome api: http://dev.minetest.net/minetest.register_biome
Nam ex spatio, omnes res venire possunt.
Why let the ground limit you when you can reach for the sky?
Back to college now, yay for sophomore year schedules. :P

Smitje
Member
Posts: 22
Joined: Wed Nov 14, 2012 20:42

Re: ground level (possible bug)

by Smitje » Post

Hi all

I have been mucking around a bit and could not find a ready made sollution so i came up with the following:
It's a small mod that logs the hight as each chunck gets initiated. I called it hfind

you can get the groundlevel or treetop level by adding or removing tree-stuff from:
local not_surface_node = {["ignore"]=true, ["air"]=true, ["default:water_source"]=true, ["default:tree"]=true, ["default:leaves"]=true, ["default:jungletree"]=true, ["default:jungleleaves"]=true }

sorry i dont have git setup so you will gave to create the files by hand (but there are only 3 of them and an optional python script for making an image)

Readme.txt, depends.txt and init.lua go in .../mods/hfind/
the python script (h_map.py) is easiest copied into the world dir as it will then find the h_file.txt, otherwise pass the path to the file as a commandline argument like this:

Code: Select all

python h_map.py path_to_h_file/h_file.txt
here is the code for README.txt:

Code: Select all

hfind by Smitje
function:
make the ground height available for mod using mapgen v6, eighter use this mod as a base or
 read the h_file from your own.
(v7 has a handy function: lastheightmap = minetest.get_mapgen_object("heightmap"), but not v6)

The h values are stored in h_file.txt in the world dir
The accompanying python script h_map.py can create an image of the data.

Stability not checked, works for me on minetest 0.4.10
Depends default
Licenses: code WTFPL

verry loosely based on:
noise23 0.1.1 by paramat
depends.txt:

Code: Select all

default
and init.lua:

Code: Select all

-- hfind by Smitje

-- converted from:
-- 23noise 0.1.1 by paramat
-- stability not verrified, works on 0.4.10
-- Depends default
-- License: code WTFPL

local YMIN = -1000
local YMAX = 2000

local not_surface_node = {["ignore"]=true, ["air"]=true, ["default:water_source"]=true, ["default:tree"]=true, ["default:leaves"]=true, ["default:jungletree"]=true, ["default:jungleleaves"]=true }
local world_path = minetest.get_worldpath()
local h_file = io.open(world_path.."/h_file.txt", "r")

print ("world_path "..world_path)

local known_chuncks = {}
local chuncks_heights = {}

-- read contents of h_file if it exists
if h_file then
	print("processing h_file")
	
	local chunck_name_line = h_file:read("*line") --should be the first chunck name
	while chunck_name_line do
		local split_line = {}
		for i in string.gmatch(chunck_name_line, "%S+") do table.insert(split_line, i)end
		local chunck_name = split_line[1] 
		known_chuncks[chunck_name] = {["status"] = split_line[2]}
		local chunck_table = {}
		local data_line = h_file:read("*line") --should be the first line of height data
		while data_line and #data_line > 79 do
			local data = {}
			for h_val in string.gmatch(data_line, "%S+") do
				if h_val then
					if h_val == "A" or h_val == "U" then
						table.insert(data, h_val)
					else
						table.insert(data, tonumber(h_val))
					end	
				end
			end
			table.insert(chunck_table, data)
			data_line = h_file:read("*line")
		end
		chuncks_heights[chunck_name] = chunck_table
		chunck_name_line = data_line
	end
end

print("imported chuncks:")
for ty, ui in pairs(known_chuncks) do print(ty, ui["status"])end

if h_file then h_file:close() end

minetest.register_on_generated(function(minp, maxp, seed)
	if minp.y < YMIN or maxp.y > YMAX then
		return  -- The chunk is outside the set height band (YMIN YMAX)
	end

	local t1 = 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 chunck_name = tostring(x0)..","..tostring(z0)
	
	print ("[hfind] name "..chunck_name.." chunk minp ("..x0.." "..y0.." "..z0..") maxp ("..x1.." "..y1.." "..z1..")")
	
	local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
	local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
	local data = vm:get_data()
	
	if known_chuncks[chunck_name] then
		if known_chuncks[chunck_name]["status"] == "complete" then
			print(chunck_name.." known and complete; not generating")
		else
			print(chunck_name.." incomplete; updating")

			local chunck_status = "complete"
			--find ground level
			for z = z0, z1 do -- for each xy plane progressing northwards
				--local ground_y = " "
				local data = {}
				for x = x0, x1 do
					local known_height = chuncks_heights[chunck_name][z-z0+1][x-x0+1]
					if known_height == "A" or known_height < y0 then
					  for y = y1, y0, -1 do
						nodename = minetest.get_node({x=x,y=y,z=z}).name
				
						if not_surface_node[nodename] then
						  if y == y0 and known_height == "A" then
							chunck_status = "incomplete"
						  end
						elseif y == y1 and known_height ~= "A" then
							chuncks_heights[chunck_name][z-z0+1][x-x0+1] = y
							chunck_status = "incomplete"
							break
						else
							chuncks_heights[chunck_name][z-z0+1][x-x0+1] = y
							break
						end
					  end
					end
				end
			end
			known_chuncks[chunck_name] = {["status"] = chunck_status}
--			for ty, ui in pairs(known_chuncks) do print(ty, ui["status"])end
		end
		
		
	else -- build the data from scratch
		print("generating")
		chunck_table = {}
		local chunck_status = "complete"
		--find ground level
		for z = z0, z1 do -- for each xy plane progressing northwards
			--local ground_y = " "
			local data = {}
			for x = x0, x1 do
				for y = y1, y0, -1 do
					nodename = minetest.get_node({x=x,y=y,z=z}).name
				
					if not_surface_node[nodename] then
						if y == y0 then
						table.insert(data, "A")
						chunck_status = "incomplete"
						end
					elseif y == y1 then
						table.insert(data, y)
						chunck_status = "incomplete"
						--ground_y = ground_y.." ".."UUU"
						break
					else
						table.insert(data, y)
						--ground_y = ground_y.." "..y
						break
					end
				end
			end
			table.insert(chunck_table, data)	
		end
		chuncks_heights[chunck_name] = chunck_table
		known_chuncks[chunck_name] = {["status"] = chunck_status}
--		for ty, ui in pairs(known_chuncks) do print(ty, ui["status"])end
	end
	
	vm:set_data(data)
	vm:set_lighting({day=0, night=0})
	vm:calc_lighting()
	vm:write_to_map(data)
	local chugent = math.ceil((os.clock() - t1) * 1000)
	print ("[hfind] "..chugent.." ms\n")
end)

--to save file:
minetest.register_on_shutdown(function()
	print("im leaving now\n\n")
	local h_file = assert(io.open(world_path.."/h_file.txt", "w"), "Failed to open 'h_file.txt'")

	for ty, ui in pairs(known_chuncks) do print(ty, ui["status"])end

	for ch, data in pairs(chuncks_heights) do
		h_file:write(ch.." "..known_chuncks[ch]["status"], "\n")
		for i, ti in pairs(data) do
			for j, tj in pairs(ti) do
				h_file:write(tj, " ")
			end
			h_file:write("\n")
		end
	end
	h_file:flush()
	h_file:close()
end)
the python script h_map.py:

Code: Select all

#! python
# produce a height map immage from a mintest h_file

import os, sys, Image

minh = 100000
maxh = -100000
minx = 100000
maxx = -100000
miny = 100000
maxy = -100000


def interp_color(color0, color1, factor):
    """Interpolate between color0 and color1.
       factor = 0 returns color0,
       factor = 1 returns color1.
    """
    r0 = int((color1[0] - color0[0])* factor) + color0[0]
    r1 = int((color1[1] - color0[1])* factor) + color0[1]
    r2 = int((color1[2] - color0[2])* factor) + color0[2]
    if len(color0) == 4:
        r3 = ((color1[3] - color0[3])* factor) + color0[3]
        return (r0, r1, r2, r3) 
    else:
        return (r0, r1, r2)


def read_chuncks(filename):
    """read the h_file and return a dict of chuncks"""
    global minh, maxh, minx, maxx, miny, maxy
    chuncks = {}

    h_file = open(filename, "r")
    line = h_file.readline()
    while line:
        chunck_name = line.split()[0]
        chuncks[chunck_name] = []
        chz = int(chunck_name.split(" ")[0].split(",")[0])
        chx = int(chunck_name.split(" ")[0].split(",")[1])
        if chz < minx : minx = chz
        if chz > maxx : maxx = chz + 80
        if chx < miny : miny = chx
        if chx > maxy : maxy = chx + 80
        
        for i in range(80):
            line = h_file.readline()
            linlist = line.split()
            for j in range(len(linlist)):
                if not (linlist[j] == "A" or linlist[j] == "U"):
                    linlist[j] = int(linlist[j])
                else:
                    linlist[j] = 0
            mi = min(linlist)
            if mi < minh:
                minh = mi
            ma = max(linlist)
            if ma > maxh and ma != "A":
                maxh = ma
            chuncks[chunck_name].append(linlist)
        line = h_file.readline()

    print"chuncks", len(chuncks), "\nmin h", minh, "max h", maxh
    print"min x", minx, "max x", maxx
    print"min y", miny, "max y", maxy

    h_file.close()
    return chuncks

def h_to_color(h):
    """ converts a h value to a color.
        values below 0 in blue, deeper is darker
        0 in beige
        values above 0 in green, higher is lighter
    """
    global minh, maxh
    lightblue = (47, 174, 255)
    darkblue = (0, 0, 109)
    lightgreen = (30, 203, 43)
    darkgreen = (34, 109, 39)
    beige = (214, 159, 42)
    white = (255, 255, 255)
    hwater = 1
    
    if h == "A" or h == "U":
        return white
    elif h < hwater:
        return interp_color(lightblue, darkblue, (float(h)/(minh-hwater)))
    elif h > hwater:
        return interp_color(darkgreen, lightgreen, (float(h)/(maxh-hwater)))
    else:
        return beige
    
def create_image(chuncks):
    """ convert the chunck data to an image """
    i_file = Image.new("RGB", (80+maxx-minx, 80+maxy-miny), (0, 0, 0))
    i_file.load()

    for i in chuncks:
        chx = int(i.split(" ")[0].split(",")[0]) - minx
        chy = maxy - miny - (int(i.split(" ")[0].split(",")[1]) - miny)
        for x in range(80):
            for y in range(80):
                #print i, chx + x, 79 + chy - y
                i_file.putpixel((chx + x, chy + 79 - y ), h_to_color(chuncks[i][y][x]))
                

    i_file.save("h_map.png")


if __name__ == "__main__":
    if len(sys.argv) == 2:
        chuncks = read_chuncks(sys.argv[1])
    else:
        chuncks = read_chuncks("h_file.txt")
    create_image(chuncks)


jin_xi
Member
Posts: 165
Joined: Mon Jul 02, 2012 18:19

Re: ground level

by jin_xi » Post

i had success using decorations, vmanips and some glue code to place stuff on the ground. it ges like this:
1. register one or more custom nodes and decorations to place them. they will be placed on the ground.

2. use vmanips to get map data and look for your custom nodes. save positions in a table.

3. use vmanips to replace custom nodes with your stuff.

no need to find the ground yourself, but help me pester hmmmm to add lua callback decoration type.

Smitje
Member
Posts: 22
Joined: Wed Nov 14, 2012 20:42

Re: ground level

by Smitje » Post

Thanks, Jin_xi,

That sounds like a quick and easy alternative, Ill have a look.

Cheers,
Smitje

Post Reply

Who is online

Users browsing this forum: No registered users and 4 guests