VoxelManip

From Minetest Developer Wiki
Jump to: navigation, search
Available in versions 0.4.8+

The Voxel Manipulator can be used to set many nodes in a specified area at once, which is useful to avoid unnecessary calculations. The metas of nodes, if they have one, do not become removed.

Contents

VoxelManip

  • An interface to the MapVoxelManipulator for Lua
  • Can be created via VoxelManip()
  • Also minetest.get_voxel_manip()
  • Specify a pmin, pmax to create a VoxelManip with map already loaded

methods:

name description comments
read_from_map(p1, p2) Reads a chunk of map from the map containing the region formed by p1 and p2. returns actual emerged pmin, actual emerged pmax, where "emerged" means that VoxelManip will "round up" to the nearest 16x16x16 map block boundary when actually creating the volume and will return the pmin and pmax of that envelope.
write_to_map() Writes the data loaded from the VoxelManip back to the map. important: data must be set using VoxelManip:set_data before calling this, otherwise use write_to_map(data)
get_data() Gets the data read into the VoxelManip object returns raw node data in the form of an array of node content ids
set_data(data) Sets the data contents of the VoxelManip object if you build the "data" array manually it must have the correct starting index and be contiguous and complete (an entry for every block in the volume)
update_map() Update map after writing chunk back to map. To be used only by VoxelManip objects created by the mod itself
set_lighting(light, p1, p2) Set the lighting within the VoxelManip to an uniform value To be used only by a VoxelManip object from minetest.get_mapgen_object. light is a table, {day=<0...15>, night=<0...15>}, (p1, p2) is the area in which lighting is set; defaults to the whole area if left out.
get_light_data() Gets the light data read into the VoxelManip object Returns an array (indicies 1 to volume) of integers ranging from 0 to 255, light = day + (night * 16);
Each value is the bitwise combination of day and night light values (0..15 each)
set_light_data(light_data) Sets the param1 (light) contents of each node in the VoxelManip expects lighting data in the same format that get_light_data() returns
get_param2_data() Gets the raw param2 data read into the VoxelManip object Only in 0.4.10+
set_param2_data(param2_data) Sets the param2 contents of each node in the VoxelManip Only in 0.4.10+
calc_lighting(p1, p2) Calculate lighting within the VoxelManip To be used only by a VoxelManip object from minetest.get_mapgen_object. (p1, p2) is the area in which lighting is set; defaults to the whole area if left out
update_liquids() Update liquid flow

VoxelArea

  • A helper class for voxel areas
  • Can be created via VoxelArea:new{MinEdge=pmin, MaxEdge=pmax}
  • Coordinates are *inclusive*, like most other things in Minetest

methods:

name description comments
getExtent() returns a 3d vector containing the size of the area formed by MinEdge and MaxEdge
getVolume() returns the volume of the area formed by MinEdge and MaxEdge
index(x, y, z) returns the index of an absolute position in a flat array starting at 1 + mapblock index. useful for things like VoxelManip, raw Schematic specifiers, PerlinNoiseMap:get2d/3dMap, and so on. Indexes are only valid for a specific extent at a specific location in the map!
indexp(p) same as above, except takes a vector
position(i) returns the absolute position vector corresponding to index i
contains(x, y, z) check if (x,y,z) is inside area formed by MinEdge and MaxEdge
containsp(p) same as above, except takes a vector
containsi(i) same as above, except takes an index
iter(minx, miny, minz, maxx, maxy, maxz) returns an iterator that returns indices from (minx,miny,minz) to (maxx,maxy,maxz) in the order of [z [y [x]]]. Very useful when you only want to handle the blocks inside a volume that is less than the "emerged" volume.
iterp(minp, maxp) same as above, except takes a vector


Tips for handling indices

Using offsets

Instead of recalculating the index using area:index or using the area:iter function, to move in specific directions you only need to add a specific number.
This is the area:index function:

function VoxelArea:index(x, y, z)
	local MinEdge = self.MinEdge
	local i = (z - MinEdge.z) * self.zstride +
			  (y - MinEdge.y) * self.ystride +
			  (x - MinEdge.x) + 1
	return math.floor(i)
end

If you have one index given and want to have the index for the position 1m above you don't need to recalculate the whole thing:

self = area
MinEdge = area.MinEdge
 
i = area:index(x, y, z)
	= (z - MinEdge.z) * self.zstride +
		(y - MinEdge.y) * self.ystride +
		(x - MinEdge.x) + 1
i2 = area:index(x, y+1, z)
	= (z - MinEdge.z) * self.zstride +
		((y+1) - MinEdge.y) * self.ystride +
		(x - MinEdge.x) + 1
	=  (z - MinEdge.z) * self.zstride +
		(y - MinEdge.y) * self.ystride + self.ystride +
		(x - MinEdge.x) + 1
	= i + self.ystride
↓
area:index(x, y+1, z) = area:index(x, y, z) + area.ystride

So if you want to move n meters in x direction:
newindex = index + n
respectively in y direction:
newindex = index + n * area.ystride
and in z direction:
newindex = index + n * area.zstride
n can be negative, of course, but it can't be float, and take care to not leave the area


Example

-- fairly imperfect code
 
local inform
local c_air = minetest.get_content_id("air")
local c_water = minetest.get_content_id("default:water_source")
 
local rarity = 0.1
local scale = 10
 
local perlin_scale = rarity*scale*100
local inverted_rarity = 1-rarity
 
minetest.register_on_generated(function(minp, maxp, seed)
 
	--avoid calculating perlin noises for unneeded places
	if maxp.y <= -150
	or minp.y >= 150 then
		return
	end
 
	local perlin1 = minetest.get_perlin(11,3, 0.5, perlin_scale)	--Get map specific perlin
	local x0,z0,x1,z1 = minp.x,minp.z,maxp.x,maxp.z	-- Assume X and Z lengths are equal
 
	if not ( perlin1:get2d( {x=x0, y=z0} ) > inverted_rarity ) 					--top left
	and not ( perlin1:get2d( { x = x0 + ( (x1-x0)/2), y=z0 } ) > inverted_rarity )--top middle
	and not (perlin1:get2d({x=x1, y=z1}) > inverted_rarity) 						--bottom right
	and not (perlin1:get2d({x=x1, y=z0+((z1-z0)/2)}) > inverted_rarity) 			--right middle
	and not (perlin1:get2d({x=x0, y=z1}) > inverted_rarity)  						--bottom left
	and not (perlin1:get2d({x=x1, y=z0}) > inverted_rarity)						--top right
	and not (perlin1:get2d({x=x0+((x1-x0)/2), y=z1}) > inverted_rarity) 			--left middle
	and not (perlin1:get2d({x=(x1-x0)/2, y=(z1-z0)/2}) > inverted_rarity) 			--middle
	and not (perlin1:get2d({x=x0, y=z1+((z1-z0)/2)}) > inverted_rarity) then		--bottom middle
		return
	end
 
	local t1 = os.clock()
	inform("[mgtest] generates at "..minetest.pos_to_string(vector.round(vector.divide(vector.add(minp, maxp), 2))))
 
	local heightmap = minetest.get_mapgen_object("heightmap")
	local hmi = 1
 
	local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
	local data = vm:get_data()
	local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
 
	for z=minp.z,maxp.z do
		for x=minp.x,maxp.x do
			local test = math.abs(perlin1:get2d({x=x, y=z}))
			if test <= 0.1 then
				for y=math.max(minp.y, math.floor(test*100-10+0.5)),math.min(heightmap[hmi]+15, maxp.y) do
					local p_pos = area:index(x, y, z)
					if y <= 1 then
						data[p_pos] = c_water
					else
						data[p_pos] = c_air
					end
				end
			end
			hmi = hmi+1
		end
	end
 
	vm:set_data(data)
	vm:calc_lighting()
	vm:update_liquids()
	vm:write_to_map()
 
	inform(string.format("[mgtest] done after ca. %.2fs", os.clock() - t1))
end)
 
function inform(msg)
	print(msg)
	--minetest.chat_send_all(msg)
end



To get the name of a block using the new api:

local name = data[p_pos]
print(minetest.get_name_from_content_id(name))

Where p_pos is the position, and using the example above. This will print out the name of the block.

See Also

Personal tools
Namespaces

Variants
Actions
Navigation
API
Toolbox