Lua Voxel Manipulators: Create chapter

master
rubenwardy 2018-03-29 16:39:34 +01:00
parent fb1df1f851
commit b551eb1d2f
3 changed files with 208 additions and 12 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.zip
# Created by https://www.gitignore.io/api/node,ruby,linux,jekyll
### Jekyll ###

View File

@ -39,58 +39,62 @@
num: 8
link: chapters/node_metadata.html
- title: Lua Voxel Manipulators
num: 9
link: chapters/lvm.html
- hr: true
- title: Privileges
num: 9
num: 10
link: chapters/privileges.html
- title: Chat and Commands
num: 10
num: 11
link: chapters/chat.html
- title: Chat Command Builder
num: 11
num: 12
link: chapters/chat_complex.html
- title: Player Physics
num: 12
num: 13
link: chapters/player_physics.html
- title: Formspecs
num: 13
num: 14
link: chapters/formspecs.html
- title: HUD
num: 14
num: 15
link: chapters/hud.html
- title: "SFINV: Inventory Formspec"
num: 15
num: 16
link: chapters/sfinv.html
- hr: true
- title: ItemStacks
num: 16
num: 17
link: chapters/itemstacks.html
- title: Inventories
num: 17
num: 18
link: chapters/inventories.html
- hr: true
- title: Automatic Error Checking
num: 18
num: 19
link: chapters/luacheck.html
- title: Releasing a Mod
num: 19
num: 20
link: chapters/releasing.html
- title: Read More
num: 20
num: 21
link: chapters/readmore.html
- hr: true

190
en/chapters/lvm.md Normal file
View File

@ -0,0 +1,190 @@
---
title: Lua Voxel Manipulators
layout: default
root: ../../
---
## Introduction
The functions outlined in the [Basic Map Operations](environment.html) chapter
are easy to use and convenient, but for large areas they are not efficient.
Every time you call `set_node` or `get_node` your mod needs to communicate with
the engine, which results in copying. Copying is slow, and will quickly kill the
performance of your game.
* [Concepts](#concepts)
* [Reading into the LVM](#reading-into-the-lvm)
* [Reading Nodes](#reading-nodes)
* [Writing Nodes](#writing-nodes)
* [Example](#example)
* [Your Turn](#your-turn)
## Concepts
Creating a Lua Voxel Manipulator allows you to load large areas of the map into
your mod's memory at once. You can then read and write to this data without
interacting with the engine at all or running any callbacks, which means that
these operations are very fast. Once done, you can then write the area back into
the engine and run any lighting calculations.
## Reading into the LVM
You can only load a cubic area into an LVM, so you need to work out the minimum
and maximum positions that you need to modify. Then you can create and read into
an LVM like so:
{% highlight lua %}
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local data = vm:get_data()
{% endhighlight %}
An LVM may not read exactly the area you tell it to, for performance reasons.
Instead it may read a larger area. The larger area is given by `emin` and `emax`,
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
it contains for you - whether that involves loading from memory, from disk, or
calling the map generator.
## Reading Nodes
You'll need to use `emin` and `emax` to work out where a node is in the data of
an LVM. There's a helper class called `VoxelArea` which handles the calculation
for you:
{% highlight lua %}
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
-- Get node's index
local idx = a:index(x, y, z)
-- Read node
print(data[idx])
{% endhighlight %}
If you run that, you'll notice that `data[vi]` is an integer. This is because
the engine doesn't store nodes using their name string, as string comparision
is slow. Instead, the engine uses a content ID. You can find out the content
ID for a particular type of node like so:
{% highlight lua %}
local c_stone = minetest.get_content_id("default:stone")
{% endhighlight %}
and then you can check whether a node is stone like so:
{% highlight lua %}
local idx = a:index(x, y, z)
if data[idx] == c_stone then
print("is stone!")
end
{% endhighlight %}
It is recommended that you find out and store the content IDs of nodes types
uring load time, as the IDs of a node type will never change. Make sure to store
the IDs in a local for performance reasons.
Nodes in an LVM data are stored in reverse co-ordinate order, so you should
always iterate in the order of `z, y, x` like so:
{% highlight lua %}
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
-- vi, voxel index, is a common variable name here
local vi = a:index(x, y, z)
if data[vi] == c_stone then
print("is stone!")
end
end
end
end
{% endhighlight %}
The reason for this touches computer architecture. Reading from RAM is rather
costly, so CPUs have multiple levels of caching. If the data a process requests
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
then a cache miss occurs so it'll fetch the data it needs from RAM. Any data
surrounding the requested data is also fetched and then replaces the data in the cache as
it's quite likely that the process will ask for data near there again. So a
good rule of optimisation is to iterate in a way that you read data one after
another, and avoid *memory thrashing*.
## Writing Nodes
First you need to set the new content ID in the data array:
{% highlight lua %}
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local vi = a:index(x, y, z)
if data[vi] == c_stone then
data[vi] = c_air
end
end
end
end
{% endhighlight %}
When you finished setting nodes in the LVM, you then need to upload the data
array to the engine:
{% highlight lua %}
vm:set_data(data)
vm:write_to_map(data)
{% endhighlight %}
You then need to update lighting and other things:
{% highlight lua %}
vm:update_map()
{% endhighlight %}
and that's it!
## Example
{% highlight lua %}
-- Get content IDs during load time, and store into a local
local c_dirt = minetest.get_content_id("default:dirt")
local c_grass = minetest.get_content_id("default:dirt_with_grass")
local function grass_to_dirt(pos1, pos2)
-- Read data into LVM
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local data = vm:get_data()
-- Modify data
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local vi = a:index(x, y, z)
if data[vi] == c_grass then
data[vi] = c_dirt
end
end
end
end
-- Write data
vm:set_data(data)
vm:write_to_map(data)
vm:update_map()
end
{% endhighlight %}
## Your Turn
* Create `replace_in_area(from, to, pos1, pos2)` which replaces all instances of
`from` with `to` in the area given, where from and to are node names.
* Make an LVM which causes mossy cobble to spread to nearby stone and cobble nodes.
Does your implementation cause mossy cobble to spread more than a distance of one each
time? How could you stop this?