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 and the on_constructs and on_destructs are ignored.

Contents

VoxelManip

It's an interface to the MapVoxelManipulator for Lua, it can be created via VoxelManip() or minetest.get_voxel_manip().
The usage varies between mapgen and no mapgen, see #Examples.

methods:

name usability in on_generated or somewhere else description comments
read_from_map(p1, p2) no mapgen 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() both 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() both 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) both 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)
set_lighting(light, p1, p2) only mapgen Set the lighting within the VoxelManip to an uniform value 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() both Gets the light data (param1 I think) 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) both (sunlight maybe mapgen only) 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() both Gets the raw param2 data read into the VoxelManip object Available since 0.4.10
set_param2_data(param2_data) both Sets the param2 contents of each node in the VoxelManip Available since 0.4.10
calc_lighting(p1, p2) mapgen only Calculate lighting within the VoxelManip (p1, p2) is the area in which lighting is set; defaults to the whole area if left out
update_liquids() both Update liquid flow
update_map() deprecated Does nothing, kept for compatibility. See [1]

VoxelArea

VoxelArea is a helper class for voxel areas, it can be created via VoxelArea:new{MinEdge=pmin, MaxEdge=pmax}.
The 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 index(p.x, p.y, p.z)
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 contains(p.x, p.y, p.z)
containsi(i) same as above, except takes an index (mostly useless)
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]]]. Use this when you want to set a cuboid of nodes.
iterp(minp, maxp) same as above, except takes two vectors

For more information, have a look at the implementation.

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


Examples

-- This sets every air node below y = 31 to cobble when generating map
 
local c_cobble = minetest.get_content_id"default:cobble"
local c_air = minetest.get_content_id"air"
 
minetest.register_on_generated(function(minp, maxp)
	-- Do nothing if the area is above 30
	if minp.y > 30 then
		return
	end
 
	-- Get the vmanip mapgen object and the nodes and VoxelArea
	local vm, emin, emax = minetest.get_mapgen_object"voxelmanip"
	local data = vm:get_data()
	local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
 
	-- Replace air with cobble
	for i in area:iter(
		minp.x, minp.y, minp.z,
		maxp.x, math.min(maxp.y, 30), maxp.z
	) do
		if data[i] == c_air then
			data[i] = c_cobble
		end
	end
 
	-- Return the changed nodes data, fix light and change map
	vm:set_data(data)
	vm:set_lighting{day=0, night=0}
	vm:calc_lighting()
	vm:write_to_map()
end)
-- This sets a sheet of cobble and wood when placing mese
 
local floor_size = 9
 
local c_cobble = minetest.get_content_id"default:cobble"
local c_wood = minetest.get_content_id"default:wood"
local sidelen = floor_size * 2 + 1 -- e.g. floor_size = 1 → 3x3 nodes
 
local function set_my_floor(pos)
	local x,y,z = pos.x,pos.y,pos.z
	local pos1 = {x = x - floor_size, y = y, z = z - floor_size}
	local pos2 = {x = x + floor_size, y = y + 1, z = z + floor_size}
 
	-- Get the vmanip object and the area and nodes
	local manip = minetest.get_voxel_manip()
	local e1, e2 = manip:read_from_map(pos1, pos2)
	local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2}
	local data = manip:get_data()
 
	-- Set cobble and wood, I chose randomly some line pattern
	local i = area:index(x - floor_size, y, z - floor_size)
	for _ = 1, sidelen do
		for _ = 1, sidelen do
			local k = i % 5
			if k == 1
			or k == 4 then
				data[i] = c_wood
			else
				data[i] = c_cobble
			end
			i = i + 1
		end
		i = i - sidelen + area.zstride
	end
 
	-- Add a wood node above the middle
	data[area:index(x, y + 1, z)] = c_wood
 
	-- Return the changed nodes, change map and show it to the players
	manip:set_data(data)
	manip:write_to_map()
	manip:update_map() -- update_map is deprecated soon
end
 
minetest.override_item("default:mese", {
	on_place = function(_,_, pt)
		if not pt then
			return
		end
		set_my_floor(pt.above)
	end
})

See Also

Personal tools
Namespaces

Variants
Actions
Navigation
API
Toolbox